/*-------------------------------------------------------------------------
 *
 * timestamp.c
 *	  内置 SQL 类型 "timestamp" 和 "interval" 的函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/timestamp.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <ctype.h>
#include <math.h>
#include <limits.h>
#include <sys/time.h>

#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/int.h"
#include "common/int128.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/float.h"
#include "utils/numeric.h"
#include "utils/sortsupport.h"

/*
 * gcc 的 -ffast-math 选项会破坏期望从
 * 表达式如 timeval / SECS_PER_HOUR 中获得精确结果的例程，其中 timeval 为 double。
 */
#ifdef __FAST_MATH__
#error -ffast-math is known to break this code
#endif

#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))

/* 在进程管理器启动时设置 */
TimestampTz PgStartTime;

/* 在配置重新加载时设置 */
TimestampTz PgReloadTime;

typedef struct
{
	Timestamp	current;
	Timestamp	finish;
	Interval	step;
	int			step_sign;
} generate_series_timestamp_fctx;

typedef struct
{
	TimestampTz current;
	TimestampTz finish;
	Interval	step;
	int			step_sign;
} generate_series_timestamptz_fctx;


static TimeOffset fc_time2t(const int fc_hour, const int fc_min, const int fc_sec, const fsec_t fc_fsec);
static Timestamp fc_dt2local(Timestamp fc_dt, int fc_timezone);
static void fc_AdjustIntervalForTypmod(Interval *fc_interval, int32 fc_typmod);
static TimestampTz fc_timestamp2timestamptz(Timestamp fc_timestamp);
static Timestamp fc_timestamptz2timestamp(TimestampTz fc_timestamp);


/* timestamptypmodin 和 timestamptztypmodin 的通用代码 */
static int32 fc_anytimestamp_typmodin(bool fc_istz, ArrayType *fc_ta)
{
	int32	   *fc_tl;
	int			fc_n;

	fc_tl = ArrayGetIntegerTypmods(fc_ta, &fc_n);

	/*
	 * 对于 TIMESTAMP 的修饰符数量不正确，语法应该不允许出错，因此我们对好的错误信息并不太紧张。
	 */
	if (fc_n != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid type modifier")));

	return anytimestamp_typmod_check(fc_istz, fc_tl[0]);
}

/* 以便 parse_expr.c 能够使用它 */
int32 anytimestamp_typmod_check(bool fc_istz, int32 fc_typmod)
{
	if (fc_typmod < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("TIMESTAMP(%d)%s precision must not be negative",
						fc_typmod, (fc_istz ? " WITH TIME ZONE" : ""))));
	if (fc_typmod > MAX_TIMESTAMP_PRECISION)
	{
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("TIMESTAMP(%d)%s precision reduced to maximum allowed, %d",
						fc_typmod, (fc_istz ? " WITH TIME ZONE" : ""),
						MAX_TIMESTAMP_PRECISION)));
		fc_typmod = MAX_TIMESTAMP_PRECISION;
	}

	return fc_typmod;
}

/* timestamptypmodout 和 timestamptztypmodout 的通用代码 */
static char * fc_anytimestamp_typmodout(bool fc_istz, int32 fc_typmod)
{
	const char *fc_tz = fc_istz ? " with time zone" : " without time zone";

	if (fc_typmod >= 0)
		return psprintf("(%d)%s", (int) fc_typmod, fc_tz);
	else
		return psprintf("%s", fc_tz);
}


/*****************************************************************************
 *	 用户 I/O 例程														 *
 *****************************************************************************/

/* timestamp_in()
 * 将字符串转换为内部格式。
 */
Datum timestamp_in(PG_FUNCTION_ARGS)
{
	char	   *fc_str = PG_GETARG_CSTRING(0);

#ifdef NOT_USED
	Oid			fc_typelem = PG_GETARG_OID(1);
#endif
	int32		fc_typmod = PG_GETARG_INT32(2);
	Timestamp	fc_result;
	fsec_t		fc_fsec;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	int			fc_tz;
	int			fc_dtype;
	int			fc_nf;
	int			fc_dterr;
	char	   *fc_field[MAXDATEFIELDS];
	int			fc_ftype[MAXDATEFIELDS];
	char		fc_workbuf[MAXDATELEN + MAXDATEFIELDS];

	fc_dterr = ParseDateTime(fc_str, fc_workbuf, sizeof(fc_workbuf),
						  fc_field, fc_ftype, MAXDATEFIELDS, &fc_nf);
	if (fc_dterr == 0)
		fc_dterr = DecodeDateTime(fc_field, fc_ftype, fc_nf, &fc_dtype, fc_tm, &fc_fsec, &fc_tz);
	if (fc_dterr != 0)
		DateTimeParseError(fc_dterr, fc_str, "timestamp");

	switch (fc_dtype)
	{
		case DTK_DATE:
			if (tm2timestamp(fc_tm, fc_fsec, NULL, &fc_result) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range: \"%s\"", fc_str)));
			break;

		case DTK_EPOCH:
			fc_result = SetEpochTimestamp();
			break;

		case DTK_LATE:
			TIMESTAMP_NOEND(fc_result);
			break;

		case DTK_EARLY:
			TIMESTAMP_NOBEGIN(fc_result);
			break;

		default:
			elog(ERROR, "unexpected dtype %d while parsing timestamp \"%s\"",
				 fc_dtype, fc_str);
			TIMESTAMP_NOEND(fc_result);
	}

	AdjustTimestampForTypmod(&fc_result, fc_typmod);

	PG_RETURN_TIMESTAMP(fc_result);
}

/* timestamp_out()
 * 将时间戳转换为外部形式。
 */
Datum timestamp_out(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(0);
	char	   *fc_result;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	fsec_t		fc_fsec;
	char		fc_buf[MAXDATELEN + 1];

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		EncodeSpecialTimestamp(fc_timestamp, fc_buf);
	else if (timestamp2tm(fc_timestamp, NULL, fc_tm, &fc_fsec, NULL, NULL) == 0)
		EncodeDateTime(fc_tm, fc_fsec, false, 0, NULL, DateStyle, fc_buf);
	else
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	fc_result = pstrdup(fc_buf);
	PG_RETURN_CSTRING(fc_result);
}

/*
 * timestamp_recv - 将外部二进制格式转换为时间戳
 */
Datum timestamp_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);

#ifdef NOT_USED
	Oid			fc_typelem = PG_GETARG_OID(1);
#endif
	int32		fc_typmod = PG_GETARG_INT32(2);
	Timestamp	fc_timestamp;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	fsec_t		fc_fsec;

	fc_timestamp = (Timestamp) pq_getmsgint64(fc_buf);

	/* 范围检查：查看timestamp_out是否需要它 */
	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		 /* ok */ ;
	else if (timestamp2tm(fc_timestamp, NULL, fc_tm, &fc_fsec, NULL, NULL) != 0 ||
			 !IS_VALID_TIMESTAMP(fc_timestamp))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	AdjustTimestampForTypmod(&fc_timestamp, fc_typmod);

	PG_RETURN_TIMESTAMP(fc_timestamp);
}

/*
 * timestamp_send - 将时间戳转换为二进制格式
 */
Datum timestamp_send(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(0);
	StringInfoData fc_buf;

	pq_begintypsend(&fc_buf);
	pq_sendint64(&fc_buf, fc_timestamp);
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

Datum timestamptypmodin(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_ta = PG_GETARG_ARRAYTYPE_P(0);

	PG_RETURN_INT32(fc_anytimestamp_typmodin(false, fc_ta));
}

Datum timestamptypmodout(PG_FUNCTION_ARGS)
{
	int32		fc_typmod = PG_GETARG_INT32(0);

	PG_RETURN_CSTRING(fc_anytimestamp_typmodout(false, fc_typmod));
}


/*
 * timestamp_support()
 *
 * 计划支持函数，用于timestamp_scale()和timestamptz_scale()
 * 长度强制转换函数（在这里我们无需区分它们）。
 */
Datum timestamp_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);
	Node	   *fc_ret = NULL;

	if (IsA(fc_rawreq, SupportRequestSimplify))
	{
		SupportRequestSimplify *fc_req = (SupportRequestSimplify *) fc_rawreq;

		fc_ret = TemporalSimplify(MAX_TIMESTAMP_PRECISION, (Node *) fc_req->fcall);
	}

	PG_RETURN_POINTER(fc_ret);
}

/* timestamp_scale()
 * 根据指定的比例因子调整时间类型。
 * 被PostgreSQL类型系统用于填充列。
 */
Datum timestamp_scale(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(0);
	int32		fc_typmod = PG_GETARG_INT32(1);
	Timestamp	fc_result;

	fc_result = fc_timestamp;

	AdjustTimestampForTypmod(&fc_result, fc_typmod);

	PG_RETURN_TIMESTAMP(fc_result);
}

/*
 * AdjustTimestampForTypmodError --- 将时间戳四舍五入以适合给定的typmod
 * 适用于时间戳或timestamptz。
 */
bool AdjustTimestampForTypmodError(Timestamp *fc_time, int32 fc_typmod, bool *fc_error)
{
	static const int64 fc_TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
		INT64CONST(1000000),
		INT64CONST(100000),
		INT64CONST(10000),
		INT64CONST(1000),
		INT64CONST(100),
		INT64CONST(10),
		INT64CONST(1)
	};

	static const int64 fc_TimestampOffsets[MAX_TIMESTAMP_PRECISION + 1] = {
		INT64CONST(500000),
		INT64CONST(50000),
		INT64CONST(5000),
		INT64CONST(500),
		INT64CONST(50),
		INT64CONST(5),
		INT64CONST(0)
	};

	if (!TIMESTAMP_NOT_FINITE(*fc_time)
		&& (fc_typmod != -1) && (fc_typmod != MAX_TIMESTAMP_PRECISION))
	{
		if (fc_typmod < 0 || fc_typmod > MAX_TIMESTAMP_PRECISION)
		{
			if (fc_error)
			{
				*fc_error = true;
				return false;
			}

			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("timestamp(%d) precision must be between %d and %d",
							fc_typmod, 0, MAX_TIMESTAMP_PRECISION)));
		}

		if (*fc_time >= INT64CONST(0))
		{
			*fc_time = ((*fc_time + fc_TimestampOffsets[fc_typmod]) / fc_TimestampScales[fc_typmod]) *
				fc_TimestampScales[fc_typmod];
		}
		else
		{
			*fc_time = -((((-*fc_time) + fc_TimestampOffsets[fc_typmod]) / fc_TimestampScales[fc_typmod])
					  * fc_TimestampScales[fc_typmod]);
		}
	}

	return true;
}

void AdjustTimestampForTypmod(Timestamp *fc_time, int32 fc_typmod)
{
	(void) AdjustTimestampForTypmodError(fc_time, fc_typmod, NULL);
}

/* timestamptz_in()
 * 将字符串转换为内部形式。
 */
Datum timestamptz_in(PG_FUNCTION_ARGS)
{
	char	   *fc_str = PG_GETARG_CSTRING(0);

#ifdef NOT_USED
	Oid			fc_typelem = PG_GETARG_OID(1);
#endif
	int32		fc_typmod = PG_GETARG_INT32(2);
	TimestampTz fc_result;
	fsec_t		fc_fsec;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	int			fc_tz;
	int			fc_dtype;
	int			fc_nf;
	int			fc_dterr;
	char	   *fc_field[MAXDATEFIELDS];
	int			fc_ftype[MAXDATEFIELDS];
	char		fc_workbuf[MAXDATELEN + MAXDATEFIELDS];

	fc_dterr = ParseDateTime(fc_str, fc_workbuf, sizeof(fc_workbuf),
						  fc_field, fc_ftype, MAXDATEFIELDS, &fc_nf);
	if (fc_dterr == 0)
		fc_dterr = DecodeDateTime(fc_field, fc_ftype, fc_nf, &fc_dtype, fc_tm, &fc_fsec, &fc_tz);
	if (fc_dterr != 0)
		DateTimeParseError(fc_dterr, fc_str, "timestamp with time zone");

	switch (fc_dtype)
	{
		case DTK_DATE:
			if (tm2timestamp(fc_tm, fc_fsec, &fc_tz, &fc_result) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range: \"%s\"", fc_str)));
			break;

		case DTK_EPOCH:
			fc_result = SetEpochTimestamp();
			break;

		case DTK_LATE:
			TIMESTAMP_NOEND(fc_result);
			break;

		case DTK_EARLY:
			TIMESTAMP_NOBEGIN(fc_result);
			break;

		default:
			elog(ERROR, "unexpected dtype %d while parsing timestamptz \"%s\"",
				 fc_dtype, fc_str);
			TIMESTAMP_NOEND(fc_result);
	}

	AdjustTimestampForTypmod(&fc_result, fc_typmod);

	PG_RETURN_TIMESTAMPTZ(fc_result);
}

/*
 * 尝试解析时区规范，并返回其时区偏移值
 * 如果可以接受的话。否则，将抛出错误。
 *
 * 注意：某些代码路径更新tm->tm_isdst，而某些则不更新；
 * 当前调用者不在乎，因此我们不必保持一致。
 */
static int fc_parse_sane_timezone(struct pg_tm *fc_tm, text *fc_zone)
{
	char		fc_tzname[TZ_STRLEN_MAX + 1];
	int			fc_rt;
	int			fc_tz;

	text_to_cstring_buffer(fc_zone, fc_tzname, sizeof(fc_tzname));

	/*
 * 查找请求的时区。首先我们尝试将其解释为一个
 * 数值时区规范；如果DecodeTimezone决定不喜欢该格式，
 * 则查看时区缩写表（以处理“EST”等情况），如果这也失败，
 * 则查看时区数据库（以处理“America/New_York”等情况）。
 * （这与时间戳输入检查的顺序相匹配；这很重要，因为
 * 时区数据库不明智地使用了一些名称与偏移缩写相同的区域名称。）
 *
 * 注意pg_tzset乐于解析DecodeTimezone会拒绝的数值输入。
 * 为了避免让它接受原本会被视为无效的输入，仅需禁止
 * 在我们的输入字符串的第一个位置包含数字。
 */
	if (isdigit((unsigned char) *fc_tzname))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid input syntax for type %s: \"%s\"",
						"numeric time zone", fc_tzname),
				 errhint("Numeric time zones must have \"-\" or \"+\" as first character.")));

	fc_rt = DecodeTimezone(fc_tzname, &fc_tz);
	if (fc_rt != 0)
	{
		char	   *fc_lowzone;
		int			fc_type,
					fc_val;
		pg_tz	   *fc_tzp;

		if (fc_rt == DTERR_TZDISP_OVERFLOW)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("numeric time zone \"%s\" out of range", fc_tzname)));
		else if (fc_rt != DTERR_BAD_FORMAT)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("time zone \"%s\" not recognized", fc_tzname)));

		/* DecodeTimezoneAbbrev需要小写输入 */
		fc_lowzone = downcase_truncate_identifier(fc_tzname,
											   strlen(fc_tzname),
											   false);
		fc_type = DecodeTimezoneAbbrev(0, fc_lowzone, &fc_val, &fc_tzp);

		if (fc_type == TZ || fc_type == DTZ)
		{
			/* 固定偏移缩写 */
			fc_tz = -fc_val;
		}
		else if (fc_type == DYNTZ)
		{
			/* 动态偏移缩写，通过指定的时间解析 */
			fc_tz = DetermineTimeZoneAbbrevOffset(fc_tm, fc_tzname, fc_tzp);
		}
		else
		{
			/* 尝试将其作为完整区域名称 */
			fc_tzp = pg_tzset(fc_tzname);
			if (fc_tzp)
				fc_tz = DetermineTimeZoneOffset(fc_tm, fc_tzp);
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("time zone \"%s\" not recognized", fc_tzname)));
		}
	}

	return fc_tz;
}

/*
 * make_timestamp_internal
 *		make_timestamp和make_timestamptz的工作马
 */
static Timestamp fc_make_timestamp_internal(int fc_year, int fc_month, int fc_day,
						int fc_hour, int fc_min, double fc_sec)
{
	struct pg_tm fc_tm;
	TimeOffset	fc_date;
	TimeOffset	fc_time;
	int			fc_dterr;
	bool		fc_bc = false;
	Timestamp	fc_result;

	fc_tm.tm_year = fc_year;
	fc_tm.tm_mon = fc_month;
	fc_tm.tm_mday = fc_day;

	/* 处理负年份作为公元前 */
	if (fc_tm.tm_year < 0)
	{
		fc_bc = true;
		fc_tm.tm_year = -fc_tm.tm_year;
	}

	fc_dterr = ValidateDate(DTK_DATE_M, false, false, fc_bc, &fc_tm);

	if (fc_dterr != 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
				 errmsg("date field value out of range: %d-%02d-%02d",
						fc_year, fc_month, fc_day)));

	if (!IS_VALID_JULIAN(fc_tm.tm_year, fc_tm.tm_mon, tm.tm_mday))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("date out of range: %d-%02d-%02d",
						fc_year, fc_month, fc_day)));

	fc_date = date2j(fc_tm.tm_year, fc_tm.tm_mon, fc_tm.tm_mday) - POSTGRES_EPOCH_JDATE;

	/* 检查时间溢出 */
	if (float_time_overflows(fc_hour, fc_min, fc_sec))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
				 errmsg("time field value out of range: %d:%02d:%02g",
						fc_hour, fc_min, fc_sec)));

	/* 这应该与 tm2time 匹配 */
	fc_time = (((fc_hour * MINS_PER_HOUR + fc_min) * SECS_PER_MINUTE)
			* USECS_PER_SEC) + (int64) rint(fc_sec * USECS_PER_SEC);

	fc_result = fc_date * USECS_PER_DAY + fc_time;
	/* 检查主要溢出 */
	if ((fc_result - fc_time) / USECS_PER_DAY != fc_date)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g",
						fc_year, fc_month, fc_day,
						fc_hour, fc_min, fc_sec)));

	/* 检查是否刚好溢出（除了时间的环绕是可以的） */
	/* 注意：我们希望允许 1999-12-31 24:00:00 */
	if ((fc_result < 0 && fc_date > 0) ||
		(fc_result > 0 && fc_date < -1))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g",
						fc_year, fc_month, fc_day,
						fc_hour, fc_min, fc_sec)));

	/* 最终范围检查捕获刚好超出范围的时间戳 */
	if (!IS_VALID_TIMESTAMP(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g",
						fc_year, fc_month, fc_day,
						fc_hour, fc_min, fc_sec)));

	return fc_result;
}

/*
 * make_timestamp() - 时间戳构造函数
 */
Datum make_timestamp(PG_FUNCTION_ARGS)
{
	int32		fc_year = PG_GETARG_INT32(0);
	int32		fc_month = PG_GETARG_INT32(1);
	int32		fc_mday = PG_GETARG_INT32(2);
	int32		fc_hour = PG_GETARG_INT32(3);
	int32		fc_min = PG_GETARG_INT32(4);
	float8		fc_sec = PG_GETARG_FLOAT8(5);
	Timestamp	fc_result;

	fc_result = fc_make_timestamp_internal(fc_year, fc_month, fc_mday,
									 fc_hour, fc_min, fc_sec);

	PG_RETURN_TIMESTAMP(fc_result);
}

/*
 * make_timestamptz() - 带时区的时间戳构造函数
 */
Datum make_timestamptz(PG_FUNCTION_ARGS)
{
	int32		fc_year = PG_GETARG_INT32(0);
	int32		fc_month = PG_GETARG_INT32(1);
	int32		fc_mday = PG_GETARG_INT32(2);
	int32		fc_hour = PG_GETARG_INT32(3);
	int32		fc_min = PG_GETARG_INT32(4);
	float8		fc_sec = PG_GETARG_FLOAT8(5);
	Timestamp	fc_result;

	fc_result = fc_make_timestamp_internal(fc_year, fc_month, fc_mday,
									 fc_hour, fc_min, fc_sec);

	PG_RETURN_TIMESTAMPTZ(fc_timestamp2timestamptz(fc_result));
}

/*
 * 构造带时区的时间戳。
 *		如上所述，但时区作为第七个参数指定。
 */
Datum make_timestamptz_at_timezone(PG_FUNCTION_ARGS)
{
	int32		fc_year = PG_GETARG_INT32(0);
	int32		fc_month = PG_GETARG_INT32(1);
	int32		fc_mday = PG_GETARG_INT32(2);
	int32		fc_hour = PG_GETARG_INT32(3);
	int32		fc_min = PG_GETARG_INT32(4);
	float8		fc_sec = PG_GETARG_FLOAT8(5);
	text	   *fc_zone = PG_GETARG_TEXT_PP(6);
	TimestampTz fc_result;
	Timestamp	fc_timestamp;
	struct pg_tm fc_tt;
	int			fc_tz;
	fsec_t		fc_fsec;

	fc_timestamp = fc_make_timestamp_internal(fc_year, fc_month, fc_mday,
										fc_hour, fc_min, fc_sec);

	if (timestamp2tm(fc_timestamp, NULL, &fc_tt, &fc_fsec, NULL, NULL) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	fc_tz = fc_parse_sane_timezone(&fc_tt, fc_zone);

	fc_result = fc_dt2local(fc_timestamp, -fc_tz);

	if (!IS_VALID_TIMESTAMP(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	PG_RETURN_TIMESTAMPTZ(fc_result);
}

/*
 * to_timestamp(double precision)
 * 将UNIX纪元转换为timestamptz。
 */
Datum float8_timestamptz(PG_FUNCTION_ARGS)
{
	float8		fc_seconds = PG_GETARG_FLOAT8(0);
	TimestampTz fc_result;

	/* 处理NaN和无限输入... */
	if (isnan(fc_seconds))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp cannot be NaN")));

	if (isinf(fc_seconds))
	{
		if (fc_seconds < 0)
			TIMESTAMP_NOBEGIN(fc_result);
		else
			TIMESTAMP_NOEND(fc_result);
	}
	else
	{
		/* 超出范围？ */
		if (fc_seconds <
			(float8) SECS_PER_DAY * (DATETIME_MIN_JULIAN - UNIX_EPOCH_JDATE)
			|| fc_seconds >=
			(float8) SECS_PER_DAY * (TIMESTAMP_END_JULIAN - UNIX_EPOCH_JDATE))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range: \"%g\"", fc_seconds)));

		/* 将UNIX纪元转换为Postgres纪元 */
		fc_seconds -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);

		fc_seconds = rint(fc_seconds * USECS_PER_SEC);
		fc_result = (int64) fc_seconds;

		/* 再次检查，以防四舍五入产生的结果刚刚超出范围 */
		if (!IS_VALID_TIMESTAMP(fc_result))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range: \"%g\"",
							PG_GETARG_FLOAT8(0))));
	}

	PG_RETURN_TIMESTAMP(fc_result);
}

/* timestamptz_out()
 * 将时间戳转换为外部形式。
 */
Datum timestamptz_out(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt = PG_GETARG_TIMESTAMPTZ(0);
	char	   *fc_result;
	int			fc_tz;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	fsec_t		fc_fsec;
	const char *fc_tzn;
	char		fc_buf[MAXDATELEN + 1];

	if (TIMESTAMP_NOT_FINITE(fc_dt))
		EncodeSpecialTimestamp(fc_dt, fc_buf);
	else if (timestamp2tm(fc_dt, &fc_tz, fc_tm, &fc_fsec, &fc_tzn, NULL) == 0)
		EncodeDateTime(fc_tm, fc_fsec, true, fc_tz, fc_tzn, DateStyle, fc_buf);
	else
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	fc_result = pstrdup(fc_buf);
	PG_RETURN_CSTRING(fc_result);
}

/*
 * timestamptz_recv - 将外部二进制格式转换为timestamptz
 */
Datum timestamptz_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);

#ifdef NOT_USED
	Oid			fc_typelem = PG_GETARG_OID(1);
#endif
	int32		fc_typmod = PG_GETARG_INT32(2);
	TimestampTz fc_timestamp;
	int			fc_tz;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	fsec_t		fc_fsec;

	fc_timestamp = (TimestampTz) pq_getmsgint64(fc_buf);

	/* 范围检查：查看timestamptz_out是否需要它 */
	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		 /* ok */ ;
	else if (timestamp2tm(fc_timestamp, &fc_tz, fc_tm, &fc_fsec, NULL, NULL) != 0 ||
			 !IS_VALID_TIMESTAMP(fc_timestamp))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	AdjustTimestampForTypmod(&fc_timestamp, fc_typmod);

	PG_RETURN_TIMESTAMPTZ(fc_timestamp);
}

/*
 * timestamptz_send - 将timestamptz转换为二进制格式
 */
Datum timestamptz_send(PG_FUNCTION_ARGS)
{
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(0);
	StringInfoData fc_buf;

	pq_begintypsend(&fc_buf);
	pq_sendint64(&fc_buf, fc_timestamp);
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

Datum timestamptztypmodin(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_ta = PG_GETARG_ARRAYTYPE_P(0);

	PG_RETURN_INT32(fc_anytimestamp_typmodin(true, fc_ta));
}

Datum timestamptztypmodout(PG_FUNCTION_ARGS)
{
	int32		fc_typmod = PG_GETARG_INT32(0);

	PG_RETURN_CSTRING(fc_anytimestamp_typmodout(true, fc_typmod));
}


/* timestamptz_scale()
 * 根据指定的比例因子调整时间类型。
 * 被PostgreSQL类型系统用于填充列。
 */
Datum timestamptz_scale(PG_FUNCTION_ARGS)
{
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(0);
	int32		fc_typmod = PG_GETARG_INT32(1);
	TimestampTz fc_result;

	fc_result = fc_timestamp;

	AdjustTimestampForTypmod(&fc_result, fc_typmod);

	PG_RETURN_TIMESTAMPTZ(fc_result);
}


/* interval_in()
 * 将字符串转换为内部形式。
 *
 * 外部格式：
 * 使用通用的日期/时间解析和解码例程。
 */
Datum interval_in(PG_FUNCTION_ARGS)
{
	char	   *fc_str = PG_GETARG_CSTRING(0);

#ifdef NOT_USED
	Oid			fc_typelem = PG_GETARG_OID(1);
#endif
	int32		fc_typmod = PG_GETARG_INT32(2);
	Interval   *fc_result;
	struct pg_itm_in fc_tt,
			   *fc_itm_in = &fc_tt;
	int			fc_dtype;
	int			fc_nf;
	int			fc_range;
	int			fc_dterr;
	char	   *fc_field[MAXDATEFIELDS];
	int			fc_ftype[MAXDATEFIELDS];
	char		fc_workbuf[256];

	fc_itm_in->tm_year = 0;
	fc_itm_in->tm_mon = 0;
	fc_itm_in->tm_mday = 0;
	fc_itm_in->tm_usec = 0;

	if (fc_typmod >= 0)
		fc_range = INTERVAL_RANGE(fc_typmod);
	else
		fc_range = INTERVAL_FULL_RANGE;

	fc_dterr = ParseDateTime(fc_str, fc_workbuf, sizeof(fc_workbuf), fc_field,
						  fc_ftype, MAXDATEFIELDS, &fc_nf);
	if (fc_dterr == 0)
		fc_dterr = DecodeInterval(fc_field, fc_ftype, fc_nf, fc_range,
							   &fc_dtype, fc_itm_in);

	/* 如果那些函数认为这是一个坏格式，尝试ISO8601风格 */
	if (fc_dterr == DTERR_BAD_FORMAT)
		fc_dterr = DecodeISO8601Interval(fc_str,
									  &fc_dtype, fc_itm_in);

	if (fc_dterr != 0)
	{
		if (fc_dterr == DTERR_FIELD_OVERFLOW)
			fc_dterr = DTERR_INTERVAL_OVERFLOW;
		DateTimeParseError(fc_dterr, fc_str, "interval");
	}

	fc_result = (Interval *) palloc(sizeof(Interval));

	switch (fc_dtype)
	{
		case DTK_DELTA:
			if (itmin2interval(fc_itm_in, fc_result) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("interval out of range")));
			break;

		default:
			elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
				 fc_dtype, fc_str);
	}

	fc_AdjustIntervalForTypmod(fc_result, fc_typmod);

	PG_RETURN_INTERVAL_P(fc_result);
}

/* interval_out()
 * 将时间跨度转换为外部形式。
 */
Datum interval_out(PG_FUNCTION_ARGS)
{
	Interval   *fc_span = PG_GETARG_INTERVAL_P(0);
	char	   *fc_result;
	struct pg_itm fc_tt,
			   *fc_itm = &fc_tt;
	char		fc_buf[MAXDATELEN + 1];

	interval2itm(*fc_span, fc_itm);
	EncodeInterval(fc_itm, IntervalStyle, fc_buf);

	fc_result = pstrdup(fc_buf);
	PG_RETURN_CSTRING(fc_result);
}

/*
 * interval_recv - 将外部二进制格式转换为interval
 */
Datum interval_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);

#ifdef NOT_USED
	Oid			fc_typelem = PG_GETARG_OID(1);
#endif
	int32		fc_typmod = PG_GETARG_INT32(2);
	Interval   *fc_interval;

	fc_interval = (Interval *) palloc(sizeof(Interval));

	fc_interval->time = pq_getmsgint64(fc_buf);
	fc_interval->day = pq_getmsgint(fc_buf, sizeof(fc_interval->day));
	fc_interval->month = pq_getmsgint(fc_buf, sizeof(fc_interval->month));

	fc_AdjustIntervalForTypmod(fc_interval, fc_typmod);

	PG_RETURN_INTERVAL_P(fc_interval);
}

/*
 * interval_send - 将interval转换为二进制格式
 */
Datum interval_send(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval = PG_GETARG_INTERVAL_P(0);
	StringInfoData fc_buf;

	pq_begintypsend(&fc_buf);
	pq_sendint64(&fc_buf, fc_interval->time);
	pq_sendint32(&fc_buf, fc_interval->day);
	pq_sendint32(&fc_buf, fc_interval->month);
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

/*
 * interval typmod 在高 16 位存储一个“范围”，在低 16 位存储一个“精度”。
 * 这两者共同定义了类型的分辨率。范围处理的分辨率粒度大于一秒，精度指定小于一秒的分辨率。
 * 这种表示能够表达所有 SQL 标准分辨率，但我们以某个位置的向右截断方式实现它们。
 * 范围是一个允许字段的位图，但只有时间上最小的字段对我们的计算有意义。
 * 精度是要保留的小于一秒的十进制位数。设置所有位 (INTERVAL_FULL_PRECISION) 给出与选择 MAX_INTERVAL_PRECISION 相同的截断语义。
 */
Datum intervaltypmodin(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_ta = PG_GETARG_ARRAYTYPE_P(0);
	int32	   *fc_tl;
	int			fc_n;
	int32		fc_typmod;

	fc_tl = ArrayGetIntegerTypmods(fc_ta, &fc_n);

	/*
	 * tl[0] - interval 范围 (字段位掩码)	 tl[1] - 精度 (可选)
	 *
	 * 请注意，我们必须验证 tl[0]，尽管通常根据语法保证其正确性 ---- 考虑 SELECT 'foo'::"interval"(1000)。
	 */
	if (fc_n > 0)
	{
		switch (fc_tl[0])
		{
			case INTERVAL_MASK(YEAR):
			case INTERVAL_MASK(MONTH):
			case INTERVAL_MASK(DAY):
			case INTERVAL_MASK(HOUR):
			case INTERVAL_MASK(MINUTE):
			case INTERVAL_MASK(SECOND):
			case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
			case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
			case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
			case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
			case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			case INTERVAL_FULL_RANGE:
				/* all OK */
				break;
			default:
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("invalid INTERVAL type modifier")));
		}
	}

	if (fc_n == 1)
	{
		if (fc_tl[0] != INTERVAL_FULL_RANGE)
			fc_typmod = INTERVAL_TYPMOD(INTERVAL_FULL_PRECISION, fc_tl[0]);
		else
			fc_typmod = -1;
	}
	else if (fc_n == 2)
	{
		if (fc_tl[1] < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("INTERVAL(%d) precision must not be negative",
							fc_tl[1])));
		if (fc_tl[1] > MAX_INTERVAL_PRECISION)
		{
			ereport(WARNING,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("INTERVAL(%d) precision reduced to maximum allowed, %d",
							fc_tl[1], MAX_INTERVAL_PRECISION)));
			fc_typmod = INTERVAL_TYPMOD(MAX_INTERVAL_PRECISION, fc_tl[0]);
		}
		else
			fc_typmod = INTERVAL_TYPMOD(fc_tl[1], fc_tl[0]);
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid INTERVAL type modifier")));
		fc_typmod = 0;				/* 保持编译器安静 */
	}

	PG_RETURN_INT32(fc_typmod);
}

Datum intervaltypmodout(PG_FUNCTION_ARGS)
{
	int32		fc_typmod = PG_GETARG_INT32(0);
	char	   *fc_res = (char *) palloc(64);
	int			fc_fields;
	int			fc_precision;
	const char *fc_fieldstr;

	if (fc_typmod < 0)
	{
		*fc_res = '\0';
		PG_RETURN_CSTRING(fc_res);
	}

	fc_fields = INTERVAL_RANGE(fc_typmod);
	fc_precision = INTERVAL_PRECISION(fc_typmod);

	switch (fc_fields)
	{
		case INTERVAL_MASK(YEAR):
			fc_fieldstr = " year";
			break;
		case INTERVAL_MASK(MONTH):
			fc_fieldstr = " month";
			break;
		case INTERVAL_MASK(DAY):
			fc_fieldstr = " day";
			break;
		case INTERVAL_MASK(HOUR):
			fc_fieldstr = " hour";
			break;
		case INTERVAL_MASK(MINUTE):
			fc_fieldstr = " minute";
			break;
		case INTERVAL_MASK(SECOND):
			fc_fieldstr = " second";
			break;
		case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
			fc_fieldstr = " year to month";
			break;
		case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
			fc_fieldstr = " day to hour";
			break;
		case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
			fc_fieldstr = " day to minute";
			break;
		case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			fc_fieldstr = " day to second";
			break;
		case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
			fc_fieldstr = " hour to minute";
			break;
		case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			fc_fieldstr = " hour to second";
			break;
		case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			fc_fieldstr = " minute to second";
			break;
		case INTERVAL_FULL_RANGE:
			fc_fieldstr = "";
			break;
		default:
			elog(ERROR, "invalid INTERVAL typmod: 0x%x", fc_typmod);
			fc_fieldstr = "";
			break;
	}

	if (fc_precision != INTERVAL_FULL_PRECISION)
		snprintf(fc_res, 64, "%s(%d)", fc_fieldstr, fc_precision);
	else
		snprintf(fc_res, 64, "%s", fc_fieldstr);

	PG_RETURN_CSTRING(fc_res);
}

/*
 * 给定 interval typmod 值，返回 typmod 允许为非零的最低有效字段的代码，例如，
 * 给定 INTERVAL DAY TO HOUR，我们希望识别“hour”。
 *
 * 结果应按字段重要性排序，这意味着我们不能使用 dt.h 中的宏 YEAR 等，
 * 因为出于某种奇怪的原因，它们并未按此方式排序。相反，任意表示
 * SECOND = 0，MINUTE = 1，HOUR = 2，DAY = 3，MONTH = 4，YEAR = 5。
 */
static int fc_intervaltypmodleastfield(int32 fc_typmod)
{
	if (fc_typmod < 0)
		return 0;				/* SECOND */

	switch (INTERVAL_RANGE(fc_typmod))
	{
		case INTERVAL_MASK(YEAR):
			return 5;			/* YEAR */
		case INTERVAL_MASK(MONTH):
			return 4;			/* MONTH */
		case INTERVAL_MASK(DAY):
			return 3;			/* DAY */
		case INTERVAL_MASK(HOUR):
			return 2;			/* HOUR */
		case INTERVAL_MASK(MINUTE):
			return 1;			/* MINUTE */
		case INTERVAL_MASK(SECOND):
			return 0;			/* SECOND */
		case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
			return 4;			/* MONTH */
		case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
			return 2;			/* HOUR */
		case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
			return 1;			/* MINUTE */
		case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			return 0;			/* SECOND */
		case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
			return 1;			/* MINUTE */
		case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			return 0;			/* SECOND */
		case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
			return 0;			/* SECOND */
		case INTERVAL_FULL_RANGE:
			return 0;			/* SECOND */
		default:
			elog(ERROR, "invalid INTERVAL typmod: 0x%x", fc_typmod);
			break;
	}
	return 0;					/* 不能到这里，但保持编译器安静 */
}


/*
 * interval_support()
 *
 * interval_scale() 的计划支持函数。
 *
 * 扁平化多余的调用到 interval_scale()。 interval typmod 很复杂，允许接受和回传所有 SQL 标准变体。
 * 出于截断的目的，它归结为单一、简单的粒度。
 */
Datum interval_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);
	Node	   *fc_ret = NULL;

	if (IsA(fc_rawreq, SupportRequestSimplify))
	{
		SupportRequestSimplify *fc_req = (SupportRequestSimplify *) fc_rawreq;
		FuncExpr   *fc_expr = fc_req->fcall;
		Node	   *fc_typmod;

		Assert(list_length(fc_expr->args) >= 2);

		fc_typmod = (Node *) lsecond(fc_expr->args);

		if (IsA(fc_typmod, Const) && !((Const *) fc_typmod)->constisnull)
		{
			Node	   *fc_source = (Node *) linitial(fc_expr->args);
			int32		fc_new_typmod = DatumGetInt32(((Const *) fc_typmod)->constvalue);
			bool		fc_noop;

			if (fc_new_typmod < 0)
				fc_noop = true;
			else
			{
				int32		fc_old_typmod = exprTypmod(fc_source);
				int			fc_old_least_field;
				int			fc_new_least_field;
				int			fc_old_precis;
				int			fc_new_precis;

				fc_old_least_field = fc_intervaltypmodleastfield(fc_old_typmod);
				fc_new_least_field = fc_intervaltypmodleastfield(fc_new_typmod);
				if (fc_old_typmod < 0)
					fc_old_precis = INTERVAL_FULL_PRECISION;
				else
					fc_old_precis = INTERVAL_PRECISION(fc_old_typmod);
				fc_new_precis = INTERVAL_PRECISION(fc_new_typmod);

				/*
				 * 如果最低字段保持不变或减小，同时精度保持不变或增大，则转换为无操作。
				 * 但精度，换言之，小于一秒的精度，只影响包括 SECOND 的范围。
				 */
				fc_noop = (fc_new_least_field <= fc_old_least_field) &&
					(fc_old_least_field > 0 /* SECOND */ ||
					 fc_new_precis >= MAX_INTERVAL_PRECISION ||
					 fc_new_precis >= fc_old_precis);
			}
			if (fc_noop)
				fc_ret = relabel_to_typmod(fc_source, fc_new_typmod);
		}
	}

	PG_RETURN_POINTER(fc_ret);
}

/* interval_scale()
 * 针对指定领域调整 interval 类型。
 * 被 PostgreSQL 类型系统用于填充列。
 */
Datum interval_scale(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval = PG_GETARG_INTERVAL_P(0);
	int32		fc_typmod = PG_GETARG_INT32(1);
	Interval   *fc_result;

	fc_result = palloc(sizeof(Interval));
	*fc_result = *fc_interval;

	fc_AdjustIntervalForTypmod(fc_result, fc_typmod);

	PG_RETURN_INTERVAL_P(fc_result);
}

/*
 *	针对指定精度调整 interval，涵盖 YEAR 到 SECOND 范围和小于一秒的精度。
 */
static void fc_AdjustIntervalForTypmod(Interval *fc_interval, int32 fc_typmod)
{
	static const int64 fc_IntervalScales[MAX_INTERVAL_PRECISION + 1] = {
		INT64CONST(1000000),
		INT64CONST(100000),
		INT64CONST(10000),
		INT64CONST(1000),
		INT64CONST(100),
		INT64CONST(10),
		INT64CONST(1)
	};

	static const int64 fc_IntervalOffsets[MAX_INTERVAL_PRECISION + 1] = {
		INT64CONST(500000),
		INT64CONST(50000),
		INT64CONST(5000),
		INT64CONST(500),
		INT64CONST(50),
		INT64CONST(5),
		INT64CONST(0)
	};

	/*
	 * 未指定的范围和精度？那么不需要调整。将 typmod 设置为 -1 是所有数据类型的惯例。
	 */
	if (fc_typmod >= 0)
	{
		int			fc_range = INTERVAL_RANGE(fc_typmod);
		int			fc_precision = INTERVAL_PRECISION(fc_typmod);

		/*
		 * 我们对具有有限字段集的间隔的解释是，最后指定的字段右侧的字段被置为零，
		 * 但左侧的字段保持有效。因此，例如，INTERVAL YEAR TO MONTH 和 INTERVAL MONTH 之间没有操作差异。
		 * 在某些情况下，我们可以有意义地强制更高阶字段为零；例如，INTERVAL DAY 可能会拒绝非零的“month”字段。
		 * 然而，当我们无法一致地做到这一点时，这似乎有点无意义。
		 * （我们不能对最高预计字段强制范围限制，因为我们没有 SQL 中的 <interval leading field precision> 的等价物。）如果我们决定
		 * 重新审视这一点，interval_support 可能需要调整。
		 *
		 * 注意：在 PG 8.4 之前，我们将有限字段集解释为实际上对给定值执行“模”操作，
		 * 可能导致高阶和低阶信息的丢失。但是标准中没有对这种行为的支持，考虑到数据一致性，这似乎也是相当不理想的。
		 * 现在我们只对低阶字段进行截断或舍入。
		 */
		if (fc_range == INTERVAL_FULL_RANGE)
		{
			/* 什么也不做... */
		}
		else if (fc_range == INTERVAL_MASK(YEAR))
		{
			fc_interval->month = (fc_interval->month / MONTHS_PER_YEAR) * MONTHS_PER_YEAR;
			fc_interval->day = 0;
			fc_interval->time = 0;
		}
		else if (fc_range == INTERVAL_MASK(MONTH))
		{
			fc_interval->day = 0;
			fc_interval->time = 0;
		}
		/* YEAR TO MONTH */
		else if (fc_range == (INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH)))
		{
			fc_interval->day = 0;
			fc_interval->time = 0;
		}
		else if (fc_range == INTERVAL_MASK(DAY))
		{
			fc_interval->time = 0;
		}
		else if (fc_range == INTERVAL_MASK(HOUR))
		{
			fc_interval->time = (fc_interval->time / USECS_PER_HOUR) *
				USECS_PER_HOUR;
		}
		else if (fc_range == INTERVAL_MASK(MINUTE))
		{
			fc_interval->time = (fc_interval->time / USECS_PER_MINUTE) *
				USECS_PER_MINUTE;
		}
		else if (fc_range == INTERVAL_MASK(SECOND))
		{
			/* 分数秒舍入将在下面处理 */
		}
		/* DAY TO HOUR */
		else if (fc_range == (INTERVAL_MASK(DAY) |
						   INTERVAL_MASK(HOUR)))
		{
			fc_interval->time = (fc_interval->time / USECS_PER_HOUR) *
				USECS_PER_HOUR;
		}
		/* DAY TO MINUTE */
		else if (fc_range == (INTERVAL_MASK(DAY) |
						   INTERVAL_MASK(HOUR) |
						   INTERVAL_MASK(MINUTE)))
		{
			fc_interval->time = (fc_interval->time / USECS_PER_MINUTE) *
				USECS_PER_MINUTE;
		}
		/* DAY TO SECOND */
		else if (fc_range == (INTERVAL_MASK(DAY) |
						   INTERVAL_MASK(HOUR) |
						   INTERVAL_MASK(MINUTE) |
						   INTERVAL_MASK(SECOND)))
		{
			/* 分数秒舍入将在下面处理 */
		}
		/* HOUR TO MINUTE */
		else if (fc_range == (INTERVAL_MASK(HOUR) |
						   INTERVAL_MASK(MINUTE)))
		{
			fc_interval->time = (fc_interval->time / USECS_PER_MINUTE) *
				USECS_PER_MINUTE;
		}
		/* HOUR TO SECOND */
		else if (fc_range == (INTERVAL_MASK(HOUR) |
						   INTERVAL_MASK(MINUTE) |
						   INTERVAL_MASK(SECOND)))
		{
			/* 分数秒舍入将在下面处理 */
		}
		/* MINUTE TO SECOND */
		else if (fc_range == (INTERVAL_MASK(MINUTE) |
						   INTERVAL_MASK(SECOND)))
		{
			/* 分数秒舍入将在下面处理 */
		}
		else
			elog(ERROR, "unrecognized interval typmod: %d", fc_typmod);

		/* 需要调整小于一秒的精度吗？ */
		if (fc_precision != INTERVAL_FULL_PRECISION)
		{
			if (fc_precision < 0 || fc_precision > MAX_INTERVAL_PRECISION)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("interval(%d) precision must be between %d and %d",
								fc_precision, 0, MAX_INTERVAL_PRECISION)));

			if (fc_interval->time >= INT64CONST(0))
			{
				fc_interval->time = ((fc_interval->time +
								   fc_IntervalOffsets[fc_precision]) /
								  fc_IntervalScales[fc_precision]) *
					fc_IntervalScales[fc_precision];
			}
			else
			{
				fc_interval->time = -(((-fc_interval->time +
									 fc_IntervalOffsets[fc_precision]) /
									fc_IntervalScales[fc_precision]) *
								   fc_IntervalScales[fc_precision]);
			}
		}
	}
}

/*
 * make_interval - 数值间隔构造函数
 */
Datum make_interval(PG_FUNCTION_ARGS)
{
	int32		fc_years = PG_GETARG_INT32(0);
	int32		fc_months = PG_GETARG_INT32(1);
	int32		fc_weeks = PG_GETARG_INT32(2);
	int32		fc_days = PG_GETARG_INT32(3);
	int32		fc_hours = PG_GETARG_INT32(4);
	int32		fc_mins = PG_GETARG_INT32(5);
	double		fc_secs = PG_GETARG_FLOAT8(6);
	Interval   *fc_result;

	/*
	 * 拒绝超出范围的输入。我们确实应该检查整数输入，
	 * 但不太清楚应该应用什么限制。
	 */
	if (isinf(fc_secs) || isnan(fc_secs))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	fc_result = (Interval *) palloc(sizeof(Interval));
	fc_result->month = fc_years * MONTHS_PER_YEAR + fc_months;
	fc_result->day = fc_weeks * 7 + fc_days;

	fc_secs = rint(fc_secs * USECS_PER_SEC);
	fc_result->time = fc_hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
		fc_mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
		(int64) fc_secs;

	PG_RETURN_INTERVAL_P(fc_result);
}

/* EncodeSpecialTimestamp()
 * 将保留的时间戳数据类型转换为字符串。
 */
void EncodeSpecialTimestamp(Timestamp fc_dt, char *fc_str)
{
	if (TIMESTAMP_IS_NOBEGIN(fc_dt))
		strcpy(fc_str, EARLY);
	else if (TIMESTAMP_IS_NOEND(fc_dt))
		strcpy(fc_str, LATE);
	else						/* 不应该发生 */
		elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}

Datum now(PG_FUNCTION_ARGS)
{
	PG_RETURN_TIMESTAMPTZ(GetCurrentTransactionStartTimestamp());
}

Datum statement_timestamp(PG_FUNCTION_ARGS)
{
	PG_RETURN_TIMESTAMPTZ(GetCurrentStatementStartTimestamp());
}

Datum clock_timestamp(PG_FUNCTION_ARGS)
{
	PG_RETURN_TIMESTAMPTZ(GetCurrentTimestamp());
}

Datum pg_postmaster_start_time(PG_FUNCTION_ARGS)
{
	PG_RETURN_TIMESTAMPTZ(PgStartTime);
}

Datum pg_conf_load_time(PG_FUNCTION_ARGS)
{
	PG_RETURN_TIMESTAMPTZ(PgReloadTime);
}

/*
 * GetCurrentTimestamp -- 获取当前操作系统时间
 *
 * 结果为 TimestampTz 值的形式，并以 gettimeofday() 系统调用的全部精度表示。
 */
TimestampTz GetCurrentTimestamp(void)
{
	TimestampTz fc_result;
	struct timeval fc_tp;

	gettimeofday(&fc_tp, NULL);

	fc_result = (TimestampTz) fc_tp.tv_sec -
		((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
	fc_result = (fc_result * USECS_PER_SEC) + fc_tp.tv_usec;

	return fc_result;
}

/*
 * GetSQLCurrentTimestamp -- 实现 CURRENT_TIMESTAMP, CURRENT_TIMESTAMP(n)
 */
TimestampTz GetSQLCurrentTimestamp(int32 fc_typmod)
{
	TimestampTz fc_ts;

	fc_ts = GetCurrentTransactionStartTimestamp();
	if (fc_typmod >= 0)
		AdjustTimestampForTypmod(&fc_ts, fc_typmod);
	return fc_ts;
}

/*
 * GetSQLLocalTimestamp -- 实现 LOCALTIMESTAMP, LOCALTIMESTAMP(n)
 */
Timestamp GetSQLLocalTimestamp(int32 fc_typmod)
{
	Timestamp	fc_ts;

	fc_ts = fc_timestamptz2timestamp(GetCurrentTransactionStartTimestamp());
	if (fc_typmod >= 0)
		AdjustTimestampForTypmod(&fc_ts, fc_typmod);
	return fc_ts;
}

/*
 * timeofday(*) -- 返回当前时间作为文本。
 */
Datum timeofday(PG_FUNCTION_ARGS)
{
	struct timeval fc_tp;
	char		fc_templ[128];
	char		fc_buf[128];
	pg_time_t	fc_tt;

	gettimeofday(&fc_tp, NULL);
	fc_tt = (pg_time_t) fc_tp.tv_sec;
	pg_strftime(fc_templ, sizeof(fc_templ), "%a %b %d %H:%M:%S.%%06d %Y %Z",
				pg_localtime(&fc_tt, session_timezone));
	snprintf(fc_buf, sizeof(fc_buf), fc_templ, fc_tp.tv_usec);

	PG_RETURN_TEXT_P(cstring_to_text(fc_buf));
}


/*
 * TimestampDifference -- 将两个时间戳之间的差异转换为整数秒和微秒
 *
 * 这通常用于计算 select(2) 的等待超时，
 * 这解释了输出格式的奇特选择。
 *
 * 两个输入必须是普通的有限时间戳（在当前用法中，
 * 它们将是 GetCurrentTimestamp() 的结果）。
 *
 * 我们期望 start_time <= stop_time。如果不是，我们返回零，
 * 因为那时我们已经超过了先前确定的 stop_time。
 */
void TimestampDifference(TimestampTz fc_start_time, TimestampTz fc_stop_time,
					long *fc_secs, int *fc_microsecs)
{
	TimestampTz fc_diff = fc_stop_time - fc_start_time;

	if (fc_diff <= 0)
	{
		*fc_secs = 0;
		*fc_microsecs = 0;
	}
	else
	{
		*fc_secs = (long) (fc_diff / USECS_PER_SEC);
		*fc_microsecs = (int) (fc_diff % USECS_PER_SEC);
	}
}

 /*
 * TimestampDifferenceMilliseconds -- 将两个时间戳之间的差异
 * 转换为整数毫秒
 *
 * 这通常用于计算 WaitLatch() 或相关函数的等待超时。
 * 选择“long”作为结果类型是为了与此保持一致。
 * 输入时间戳之间的距离不应太远，以免导致“long”的溢出
 * （在具有 32 位“long”的机器上，这将在大约 25 天后发生）。
 *
 * 两个输入必须是普通的有限时间戳（在当前用法中，
 * 它们将是 GetCurrentTimestamp() 的结果）。
 *
 * 我们期望 start_time <= stop_time。如果不是，我们返回零，
 * 因为那时我们已经超过了先前确定的 stop_time。
 *
 * 注意，我们会将任何小于一毫秒的部分向上舍入，因为
 * 等待不到预期超时是不可取的。
 */
long TimestampDifferenceMilliseconds(TimestampTz fc_start_time, TimestampTz fc_stop_time)
{
	TimestampTz fc_diff = fc_stop_time - fc_start_time;

	if (fc_diff <= 0)
		return 0;
	else
		return (long) ((fc_diff + 999) / 1000);
}

 /*
 * TimestampDifferenceExceeds -- 报告两个时间戳之间的差异是否 >= 阈值
 * （以毫秒表示）
 *
 * 两个输入必须是普通的有限时间戳（在当前用法中，
 * 它们将是 GetCurrentTimestamp() 的结果）。
 */
bool TimestampDifferenceExceeds(TimestampTz fc_start_time,
						   TimestampTz fc_stop_time,
						   int fc_msec)
{
	TimestampTz fc_diff = fc_stop_time - fc_start_time;

	return (fc_diff >= fc_msec * INT64CONST(1000));
}

 /*
 * 将 time_t 转换为 TimestampTz。
 *
 * 我们在 Postgres 内部不使用 time_t，但这是为了
 * 供需要解释，例如 stat(2) 结果的函数使用。
 *
 * 为了避免函数的 ABI 根据 time_t 的宽度而变化，
 * 我们将参数声明为 pg_time_t，它与 time_t 兼容转换，
 * 但始终为 64 位宽（除非平台没有 64 位类型）。
 * 这一细节应该对调用者是透明的，至少在源代码级别。
 */
TimestampTz time_t_to_timestamptz(pg_time_t fc_tm)
{
	TimestampTz fc_result;

	fc_result = (TimestampTz) fc_tm -
		((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
	fc_result *= USECS_PER_SEC;

	return fc_result;
}

/*
 * 将 TimestampTz 转换为 time_t。
 *
 * 这同样只是边缘有用，但某些地方需要它。
 *
 * 为了避免函数的 ABI 根据 time_t 的宽度而变化，
 * 我们将结果声明为 pg_time_t，它与 time_t 兼容转换，
 * 但始终为 64 位宽（除非平台没有 64 位类型）。
 * 这一细节在调用者看来应该是不可见的，至少在源代码级别上是这样的。
 */
pg_time_t timestamptz_to_time_t(TimestampTz fc_t)
{
	pg_time_t	fc_result;

	fc_result = (pg_time_t) (fc_t / USECS_PER_SEC +
						  ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY));

	return fc_result;
}

/*
 * 生成 TimestampTz 的 C 字符串表示。
 *
 * 这主要用于发出消息。与 timestamptz_out 的主要区别在于
 * 我们强制输出格式为 ISO。还要注意结果是在一个静态缓冲区中，而不是 pstrdup'd。
 *
 * 另请参见 pg_strftime。
 */
const char * timestamptz_to_str(TimestampTz fc_t)
{
	static char fc_buf[MAXDATELEN + 1];
	int			fc_tz;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	fsec_t		fc_fsec;
	const char *fc_tzn;

	if (TIMESTAMP_NOT_FINITE(fc_t))
		EncodeSpecialTimestamp(fc_t, fc_buf);
	else if (timestamp2tm(fc_t, &fc_tz, fc_tm, &fc_fsec, &fc_tzn, NULL) == 0)
		EncodeDateTime(fc_tm, fc_fsec, true, fc_tz, fc_tzn, USE_ISO_DATES, fc_buf);
	else
		strlcpy(fc_buf, "(timestamp out of range)", sizeof(fc_buf));

	return fc_buf;
}


void dt2time(Timestamp fc_jd, int *fc_hour, int *fc_min, int *fc_sec, fsec_t *fc_fsec)
{
	TimeOffset	fc_time;

	fc_time = fc_jd;

	*fc_hour = fc_time / USECS_PER_HOUR;
	fc_time -= (*fc_hour) * USECS_PER_HOUR;
	*fc_min = fc_time / USECS_PER_MINUTE;
	fc_time -= (*fc_min) * USECS_PER_MINUTE;
	*fc_sec = fc_time / USECS_PER_SEC;
	*fc_fsec = fc_time - (*fc_sec * USECS_PER_SEC);
}								/* 将此字段标记为已完成 */


/*
 * timestamp2tm() - 将时间戳数据类型转换为 POSIX 时间结构。
 *
 * 请注意，年份不是基于 1900，而是一个明确的完整值。
 * 此外，月份是从 1 开始，而不是从 0 开始。
 * 返回：
 *	 0 表示成功
 *	-1 表示超出范围
 *
 * 如果 attimezone 为 NULL，则将使用全局时区设置。
 */
int timestamp2tm(Timestamp fc_dt, int *fc_tzp, struct pg_tm *fc_tm, fsec_t *fc_fsec, const char **fc_tzn, pg_tz *fc_attimezone)
{
	Timestamp	fc_date;
	Timestamp	fc_time;
	pg_time_t	fc_utime;

	/* 如果调用者请求默认值，则使用会话时区 */
	if (fc_attimezone == NULL)
		fc_attimezone = session_timezone;

	fc_time = fc_dt;
	TMODULO(fc_time, fc_date, USECS_PER_DAY);

	if (fc_time < INT64CONST(0))
	{
		fc_time += USECS_PER_DAY;
		fc_date -= 1;
	}

	/* 添加偏移量以从 J2000 返回到标准 Julian 日期 */
	fc_date += POSTGRES_EPOCH_JDATE;

	/* Julian 日 routine 不适用于负 Julian 日 */
	if (fc_date < 0 || fc_date > (Timestamp) INT_MAX)
		return -1;

	j2date((int) fc_date, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
	dt2time(fc_time, &fc_tm->tm_hour, &fc_tm->tm_min, &fc_tm->tm_sec, fc_fsec);

	/* 如果不需要 TZ 转换，则完成 */
	if (fc_tzp == NULL)
	{
		fc_tm->tm_isdst = -1;
		fc_tm->tm_gmtoff = 0;
		fc_tm->tm_zone = NULL;
		if (fc_tzn != NULL)
			*fc_tzn = NULL;
		return 0;
	}

	/*
	 * 如果时间在 pg_time_t 的范围内，则使用 pg_localtime()
	 * 转换为本地时区。
	 *
	 * 首先，转换为整数时间戳，避免可能的
	 * 平台特定的错误，并调整为 Unix 纪元。然后查看
	 * 我们是否可以在没有损失的情况下转换为 pg_time_t。这
	 * 种编程避免了对 pg_time_t 宽度的任何假设，因此
	 * 即使在没有 int64 的机器上，它也应该正常工作。
	 */
	fc_dt = (fc_dt - *fc_fsec) / USECS_PER_SEC +
		(POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY;
	fc_utime = (pg_time_t) fc_dt;
	if ((Timestamp) fc_utime == fc_dt)
	{
		struct pg_tm *fc_tx = pg_localtime(&fc_utime, fc_attimezone);

		fc_tm->tm_year = fc_tx->tm_year + 1900;
		fc_tm->tm_mon = fc_tx->tm_mon + 1;
		fc_tm->tm_mday = fc_tx->tm_mday;
		fc_tm->tm_hour = fc_tx->tm_hour;
		fc_tm->tm_min = fc_tx->tm_min;
		fc_tm->tm_sec = fc_tx->tm_sec;
		fc_tm->tm_isdst = fc_tx->tm_isdst;
		fc_tm->tm_gmtoff = fc_tx->tm_gmtoff;
		fc_tm->tm_zone = fc_tx->tm_zone;
		*fc_tzp = -fc_tm->tm_gmtoff;
		if (fc_tzn != NULL)
			*fc_tzn = fc_tm->tm_zone;
	}
	else
	{
		/*
		 * 当超出 pg_time_t 的范围时，将其视为 GMT
		 */
		*fc_tzp = 0;
		/* 将其标记为 *没有* 可用时区 */
		fc_tm->tm_isdst = -1;
		fc_tm->tm_gmtoff = 0;
		fc_tm->tm_zone = NULL;
		if (fc_tzn != NULL)
			*fc_tzn = NULL;
	}

	return 0;
}


/* tm2timestamp()
 * 将 tm 结构转换为时间戳数据类型。
 * 请注意，年份不是基于 1900，而是一个明确的完整值。
 * 此外，月份是从 1 开始，而不是从 0 开始。
 *
 * 如果失败（值超出范围）返回 -1。
 */
int tm2timestamp(struct pg_tm *fc_tm, fsec_t fc_fsec, int *fc_tzp, Timestamp *fc_result)
{
	TimeOffset	fc_date;
	TimeOffset	fc_time;

	/* 防止 Julian 日 routines 溢出 */
	if (!IS_VALID_JULIAN(fc_tm->tm_year, fc_tm->tm_mon, tm->tm_mday))
	{
		*fc_result = 0;			/* 保持编译器安静 */
		return -1;
	}

	fc_date = date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday) - POSTGRES_EPOCH_JDATE;
	fc_time = fc_time2t(fc_tm->tm_hour, fc_tm->tm_min, fc_tm->tm_sec, fc_fsec);

	*fc_result = fc_date * USECS_PER_DAY + fc_time;
	/* 检查主要溢出 */
	if ((*fc_result - fc_time) / USECS_PER_DAY != fc_date)
	{
		*fc_result = 0;			/* 保持编译器安静 */
		return -1;
	}
	/* 检查是否刚好溢出（除了时间的环绕是可以的） */
	/* 注意：我们希望允许 1999-12-31 24:00:00 */
	if ((*fc_result < 0 && fc_date > 0) ||
		(*fc_result > 0 && fc_date < -1))
	{
		*fc_result = 0;			/* 保持编译器安静 */
		return -1;
	}
	if (fc_tzp != NULL)
		*fc_result = fc_dt2local(*fc_result, -(*fc_tzp));

	/* 最终范围检查捕获刚好超出范围的时间戳 */
	if (!IS_VALID_TIMESTAMP(*fc_result))
	{
		*fc_result = 0;			/* 保持编译器安静 */
		return -1;
	}

	return 0;
}


/* interval2itm()
 * 将 Interval 转换为 pg_itm 结构。
 * 注意：溢出是不可能的，因为 pg_itm 字段的宽度足以容纳所有可能的转换结果。
 */
void interval2itm(Interval fc_span, struct pg_itm *fc_itm)
{
	TimeOffset	fc_time;
	TimeOffset	fc_tfrac;

	fc_itm->tm_year = fc_span.month / MONTHS_PER_YEAR;
	fc_itm->tm_mon = fc_span.month % MONTHS_PER_YEAR;
	fc_itm->tm_mday = fc_span.day;
	fc_time = fc_span.time;

	fc_tfrac = fc_time / USECS_PER_HOUR;
	fc_time -= fc_tfrac * USECS_PER_HOUR;
	fc_itm->tm_hour = fc_tfrac;
	fc_tfrac = fc_time / USECS_PER_MINUTE;
	fc_time -= fc_tfrac * USECS_PER_MINUTE;
	fc_itm->tm_min = (int) fc_tfrac;
	fc_tfrac = fc_time / USECS_PER_SEC;
	fc_time -= fc_tfrac * USECS_PER_SEC;
	fc_itm->tm_sec = (int) fc_tfrac;
	fc_itm->tm_usec = (int) fc_time;
}

/* itm2interval()
 * 将 pg_itm 结构转换为 Interval。
 * 如果 OK 返回 0，溢出返回 -1。
 */
int itm2interval(struct pg_itm *fc_itm, Interval *fc_span)
{
	int64		fc_total_months = (int64) fc_itm->tm_year * MONTHS_PER_YEAR + fc_itm->tm_mon;

	if (fc_total_months > INT_MAX || fc_total_months < INT_MIN)
		return -1;
	fc_span->month = (int32) fc_total_months;
	fc_span->day = fc_itm->tm_mday;
	if (pg_mul_s64_overflow(fc_itm->tm_hour, USECS_PER_HOUR,
							&fc_span->time))
		return -1;
	/* tm_min, tm_sec 是 32 位，因此中间产品不能溢出 */
	if (pg_add_s64_overflow(fc_span->time, fc_itm->tm_min * USECS_PER_MINUTE,
							&fc_span->time))
		return -1;
	if (pg_add_s64_overflow(fc_span->time, fc_itm->tm_sec * USECS_PER_SEC,
							&fc_span->time))
		return -1;
	if (pg_add_s64_overflow(fc_span->time, fc_itm->tm_usec,
							&fc_span->time))
		return -1;
	return 0;
}

/* itmin2interval()
 * 将 pg_itm_in 结构转换为 Interval。
 * 如果 OK 返回 0，溢出返回 -1。
 */
int itmin2interval(struct pg_itm_in *fc_itm_in, Interval *fc_span)
{
	int64		fc_total_months = (int64) fc_itm_in->tm_year * MONTHS_PER_YEAR + fc_itm_in->tm_mon;

	if (fc_total_months > INT_MAX || fc_total_months < INT_MIN)
		return -1;
	fc_span->month = (int32) fc_total_months;
	fc_span->day = fc_itm_in->tm_mday;
	fc_span->time = fc_itm_in->tm_usec;
	return 0;
}

static TimeOffset
fc_time2t(const int fc_hour, const int fc_min, const int fc_sec, const fsec_t fc_fsec)
{
	return (((((fc_hour * MINS_PER_HOUR) + fc_min) * SECS_PER_MINUTE) + fc_sec) * USECS_PER_SEC) + fc_fsec;
}

static Timestamp fc_dt2local(Timestamp fc_dt, int fc_tz)
{
	fc_dt -= (fc_tz * USECS_PER_SEC);
	return fc_dt;
}


/*****************************************************************************
 *	 公共例程														 *
 *****************************************************************************/


Datum timestamp_finite(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(0);

	PG_RETURN_BOOL(!TIMESTAMP_NOT_FINITE(fc_timestamp));
}

Datum interval_finite(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(true);
}


/*----------------------------------------------------------
 * 时间戳的关系运算符。
 *---------------------------------------------------------*/

void GetEpochTime(struct pg_tm *fc_tm)
{
	struct pg_tm *fc_t0;
	pg_time_t	fc_epoch = 0;

	fc_t0 = pg_gmtime(&fc_epoch);

	if (fc_t0 == NULL)
		elog(ERROR, "could not convert epoch to timestamp: %m");

	fc_tm->tm_year = fc_t0->tm_year;
	fc_tm->tm_mon = fc_t0->tm_mon;
	fc_tm->tm_mday = fc_t0->tm_mday;
	fc_tm->tm_hour = fc_t0->tm_hour;
	fc_tm->tm_min = fc_t0->tm_min;
	fc_tm->tm_sec = fc_t0->tm_sec;

	fc_tm->tm_year += 1900;
	fc_tm->tm_mon++;
}

Timestamp SetEpochTimestamp(void)
{
	Timestamp	fc_dt;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;

	GetEpochTime(fc_tm);
	/* 我们不再测试失败 ... */
	tm2timestamp(fc_tm, 0, NULL, &fc_dt);

	return fc_dt;
}								/* SetEpochTimestamp() */

/*
 * 我们当前在时间戳和 timestamptz 之间共享一些代码。
 * 比较函数就是其中之一。 - thomas 2001-09-25
 *
 *		timestamp_relop - timestamp1 是否 relop timestamp2
 */
int timestamp_cmp_internal(Timestamp fc_dt1, Timestamp fc_dt2)
{
	return (fc_dt1 < fc_dt2) ? -1 : ((fc_dt1 > fc_dt2) ? 1 : 0);
}

Datum timestamp_eq(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_internal(fc_dt1, fc_dt2) == 0);
}

Datum timestamp_ne(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_internal(fc_dt1, fc_dt2) != 0);
}

Datum timestamp_lt(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_internal(fc_dt1, fc_dt2) < 0);
}

Datum timestamp_gt(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_internal(fc_dt1, fc_dt2) > 0);
}

Datum timestamp_le(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_internal(fc_dt1, fc_dt2) <= 0);
}

Datum timestamp_ge(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_internal(fc_dt1, fc_dt2) >= 0);
}

Datum timestamp_cmp(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_INT32(timestamp_cmp_internal(fc_dt1, fc_dt2));
}

#if SIZEOF_DATUM < 8
/* 注意：这同样适用于 timestamptz */
static int timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
{
	Timestamp	a = DatumGetTimestamp(x);
	Timestamp	b = DatumGetTimestamp(y);

	return timestamp_cmp_internal(a, b);
}
#endif

Datum timestamp_sortsupport(PG_FUNCTION_ARGS)
{
	SortSupport fc_ssup = (SortSupport) PG_GETARG_POINTER(0);

#if SIZEOF_DATUM >= 8

	/*
	 * 如果此构建具有按值传递的时间戳，则可以使用标准
	 * 比较函数。
	 */
	fc_ssup->comparator = ssup_datum_signed_cmp;
#else
	ssup->comparator = timestamp_fastcmp;
#endif
	PG_RETURN_VOID();
}

Datum timestamp_hash(PG_FUNCTION_ARGS)
{
	return hashint8(fcinfo);
}

Datum timestamp_hash_extended(PG_FUNCTION_ARGS)
{
	return hashint8extended(fcinfo);
}

/*
 * 时间戳与 timestamptz 之间的跨类型比较函数
 */

int32 timestamp_cmp_timestamptz_internal(Timestamp fc_timestampVal, TimestampTz fc_dt2)
{
	TimestampTz fc_dt1;
	int			fc_overflow;

	fc_dt1 = timestamp2timestamptz_opt_overflow(fc_timestampVal, &fc_overflow);
	if (fc_overflow > 0)
	{
		/* dt1 大于任何有限时间戳，但小于无穷大 */
		return TIMESTAMP_IS_NOEND(fc_dt2) ? -1 : +1;
	}
	if (fc_overflow < 0)
	{
		/* dt1 小于任何有限时间戳，但大于 -infinity */
		return TIMESTAMP_IS_NOBEGIN(fc_dt2) ? +1 : -1;
	}

	return timestamptz_cmp_internal(fc_dt1, fc_dt2);
}

Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt2) == 0);
}

Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt2) != 0);
}

Datum timestamp_lt_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt2) < 0);
}

Datum timestamp_gt_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt2) > 0);
}

Datum timestamp_le_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt2) <= 0);
}

Datum timestamp_ge_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt2) >= 0);
}

Datum timestamp_cmp_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);

	PG_RETURN_INT32(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt2));
}

Datum timestamptz_eq_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt1) == 0);
}

Datum timestamptz_ne_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt1) != 0);
}

Datum timestamptz_lt_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt1) > 0);
}

Datum timestamptz_gt_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt1) < 0);
}

Datum timestamptz_le_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt1) >= 0);
}

Datum timestamptz_ge_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt1) <= 0);
}

Datum timestamptz_cmp_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	Timestamp	fc_timestampVal = PG_GETARG_TIMESTAMP(1);

	PG_RETURN_INT32(-timestamp_cmp_timestamptz_internal(fc_timestampVal, fc_dt1));
}


/*
 *		interval_relop	- interval1 是否 relop interval2
 *
 * Interval 比较基于将 interval 值转换为线性
 * 表示，以时间字段的单位（在整数时间戳的情况下为微秒）表示，
 * 假设一天始终为 24 小时，假设一个月始终为 30 天。为了避免溢出，
 * 我们需要一个比 int64 更宽的数据类型用于线性表示，因此使用 INT128。
 */

static inline INT128
fc_interval_cmp_value(const Interval *fc_interval)
{
	INT128		fc_span;
	int64		fc_days;

	/*
	 * 将月份和日期字段合并为一个整数天数。
	 * 由于输入为 int32，因此这里使用 int64 算法足够。
	 */
	fc_days = fc_interval->month * INT64CONST(30);
	fc_days += fc_interval->day;

	/* 将时间字段扩展到 128 位 */
	fc_span = int64_to_int128(fc_interval->time);

	/* 将天数扩大到微秒，形成一个 128 位的乘积 */
	int128_add_int64_mul_int64(&fc_span, fc_days, USECS_PER_DAY);

	return fc_span;
}

static int fc_interval_cmp_internal(Interval *fc_interval1, Interval *fc_interval2)
{
	INT128		fc_span1 = fc_interval_cmp_value(fc_interval1);
	INT128		fc_span2 = fc_interval_cmp_value(fc_interval2);

	return int128_compare(fc_span1, fc_span2);
}

Datum interval_eq(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);

	PG_RETURN_BOOL(fc_interval_cmp_internal(fc_interval1, fc_interval2) == 0);
}

Datum interval_ne(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);

	PG_RETURN_BOOL(fc_interval_cmp_internal(fc_interval1, fc_interval2) != 0);
}

Datum interval_lt(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);

	PG_RETURN_BOOL(fc_interval_cmp_internal(fc_interval1, fc_interval2) < 0);
}

Datum interval_gt(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);

	PG_RETURN_BOOL(fc_interval_cmp_internal(fc_interval1, fc_interval2) > 0);
}

Datum interval_le(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);

	PG_RETURN_BOOL(fc_interval_cmp_internal(fc_interval1, fc_interval2) <= 0);
}

Datum interval_ge(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);

	PG_RETURN_BOOL(fc_interval_cmp_internal(fc_interval1, fc_interval2) >= 0);
}

Datum interval_cmp(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);

	PG_RETURN_INT32(fc_interval_cmp_internal(fc_interval1, fc_interval2));
}

/*
 * 间隔的哈希
 *
 * 我们必须为 interval_cmp_internal() 认为相等的值生成相等的哈希值。
 * 所以，按照同样的方式计算净跨度，然后计算哈希。
 */
Datum interval_hash(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval = PG_GETARG_INTERVAL_P(0);
	INT128		fc_span = fc_interval_cmp_value(fc_interval);
	int64		fc_span64;

	/*
	 * 仅使用最低有效的 64 位进行哈希。高 64 位
	 * 很少提供任何有用的信息，而且我们必须这样做
	 * 是为了与在引入 INT128 之前计算的哈希兼容。
	 */
	fc_span64 = int128_to_int64(fc_span);

	return DirectFunctionCall1(hashint8, Int64GetDatumFast(fc_span64));
}

Datum interval_hash_extended(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval = PG_GETARG_INTERVAL_P(0);
	INT128		fc_span = fc_interval_cmp_value(fc_interval);
	int64		fc_span64;

	/* 与 interval_hash 相同的方法 */
	fc_span64 = int128_to_int64(fc_span);

	return DirectFunctionCall2(hashint8extended, Int64GetDatumFast(fc_span64),
							   PG_GETARG_DATUM(1));
}

/* overlaps_timestamp() --- 实现 SQL OVERLAPS 运算符。
 *
 * 算法符合 SQL 规范。这比你想象的要难得多，
 * 因为规范要求我们在某些情况下提供非空答案
 * 而某些输入为空。
 */
Datum overlaps_timestamp(PG_FUNCTION_ARGS)
{
	/*
	 * 参数是时间戳，但我们将它们保留为通用 Datum，
	 * 以避免在值和引用形式之间进行不必要的转换——更不用说可能的空指针解引用。
	 */
	Datum		fc_ts1 = PG_GETARG_DATUM(0);
	Datum		fc_te1 = PG_GETARG_DATUM(1);
	Datum		fc_ts2 = PG_GETARG_DATUM(2);
	Datum		fc_te2 = PG_GETARG_DATUM(3);
	bool		fc_ts1IsNull = PG_ARGISNULL(0);
	bool		fc_te1IsNull = PG_ARGISNULL(1);
	bool		fc_ts2IsNull = PG_ARGISNULL(2);
	bool		fc_te2IsNull = PG_ARGISNULL(3);

#define TIMESTAMP_GT(t1,t2) \
	DatumGetBool(DirectFunctionCall2(timestamp_gt,t1,t2))
#define TIMESTAMP_LT(t1,t2) \
	DatumGetBool(DirectFunctionCall2(timestamp_lt,t1,t2))

	/*
	 * 如果区间1的两个端点都是null，结果为null（未知）。
	 * 如果只有一个端点为null，则取ts1为非null值。 否则，取ts1为较小的端点。
	 */
	if (fc_ts1IsNull)
	{
		if (fc_te1IsNull)
			PG_RETURN_NULL();
		/* 用非空值交换空值 */
		fc_ts1 = fc_te1;
		fc_te1IsNull = true;
	}
	else if (!fc_te1IsNull)
	{
		if (TIMESTAMP_GT(fc_ts1, fc_te1))
		{
			Datum		fc_tt = fc_ts1;

			fc_ts1 = fc_te1;
			fc_te1 = fc_tt;
		}
	}

	/* 对于区间 2 也是如此。 */
	if (fc_ts2IsNull)
	{
		if (fc_te2IsNull)
			PG_RETURN_NULL();
		/* 用非空值交换空值 */
		fc_ts2 = fc_te2;
		fc_te2IsNull = true;
	}
	else if (!fc_te2IsNull)
	{
		if (TIMESTAMP_GT(fc_ts2, fc_te2))
		{
			Datum		fc_tt = fc_ts2;

			fc_ts2 = fc_te2;
			fc_te2 = fc_tt;
		}
	}

	/*
	 * 此时，ts1 和 ts2 均不为空，因此我们可以考虑三种情况：
	 * ts1 > ts2，ts1 < ts2，ts1 = ts2
	 */
	if (TIMESTAMP_GT(fc_ts1, fc_ts2))
	{
		/*
		 * 这个情况是ts1 < te2 OR te1 < te2，这看起来可能冗余，
		 * 但在存在空值时并不完全如此。
		 */
		if (fc_te2IsNull)
			PG_RETURN_NULL();
		if (TIMESTAMP_LT(fc_ts1, fc_te2))
			PG_RETURN_BOOL(true);
		if (fc_te1IsNull)
			PG_RETURN_NULL();

		/*
		 * 如果te1不为空，那么我们之前有ts1 <= te1，并且我们刚刚发现ts1 >= te2，
		 * 因此te1 >= te2。
		 */
		PG_RETURN_BOOL(false);
	}
	else if (TIMESTAMP_LT(fc_ts1, fc_ts2))
	{
		/* 这种情况是 ts2 < te1 或 te2 < te1 */
		if (fc_te1IsNull)
			PG_RETURN_NULL();
		if (TIMESTAMP_LT(fc_ts2, fc_te1))
			PG_RETURN_BOOL(true);
		if (fc_te2IsNull)
			PG_RETURN_NULL();

		/*
		 * 如果te2不为null，则我们以上有ts2 <= te2，并且我们刚刚发现ts2 >= te1，因此te2 >= te1。
		 */
		PG_RETURN_BOOL(false);
	}
	else
	{
		
/*
		 * 对于 ts1 = ts2，规范中说 te1 <> te2 或 te1 = te2，这是一种相当愚蠢的说法，意思是“如果两者都非空，则为真，否则为 null”。
		 */
		if (fc_te1IsNull || fc_te2IsNull)
			PG_RETURN_NULL();
		PG_RETURN_BOOL(true);
	}

#undef TIMESTAMP_GT
#undef TIMESTAMP_LT
}


/*----------------------------------------------------------
 *	日期/时间上的 "算术" 运算符。
 *---------------------------------------------------------*/

Datum timestamp_smaller(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);
	Timestamp	fc_result;

	/* 使用 timestamp_cmp_internal 以确保这与比较一致 */
	if (timestamp_cmp_internal(fc_dt1, fc_dt2) < 0)
		fc_result = fc_dt1;
	else
		fc_result = fc_dt2;
	PG_RETURN_TIMESTAMP(fc_result);
}

Datum timestamp_larger(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);
	Timestamp	fc_result;

	if (timestamp_cmp_internal(fc_dt1, fc_dt2) > 0)
		fc_result = fc_dt1;
	else
		fc_result = fc_dt2;
	PG_RETURN_TIMESTAMP(fc_result);
}


Datum timestamp_mi(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);
	Interval   *fc_result;

	fc_result = (Interval *) palloc(sizeof(Interval));

	if (TIMESTAMP_NOT_FINITE(fc_dt1) || TIMESTAMP_NOT_FINITE(fc_dt2))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("cannot subtract infinite timestamps")));

	fc_result->time = fc_dt1 - fc_dt2;

	fc_result->month = 0;
	fc_result->day = 0;

	
/*----------
	 *	这段是错误的，但删除它会破坏很多回归测试。
	 *	例如：
	 *
	 *	test=> SET timezone = 'EST5EDT';
	 *	test=> SELECT
	 *	test-> ('2005-10-30 13:22:00-05'::timestamptz -
	 *	test(>	'2005-10-29 13:22:00-04'::timestamptz);
	 *	?column?
	 *	----------------
	 *	 1 day 01:00:00
	 *	 (1 row)
	 *
	 *	所以将其添加到第一个时间戳会得到：
	 *
	 *	 test=> SELECT
	 *	 test-> ('2005-10-29 13:22:00-04'::timestamptz +
	 *	 test(> ('2005-10-30 13:22:00-05'::timestamptz -
	 *	 test(>  '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
	 *		timezone
	 *	--------------------
	 *	2005-10-30 14:22:00
	 *	(1 row)
	 *----------
	 */
	fc_result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
												   IntervalPGetDatum(fc_result)));

	PG_RETURN_INTERVAL_P(fc_result);
}

/*
 *	interval_justify_interval()
 *
 *	调整间隔，使得“月”、“天”和“时间”部分在
 *	通常范围内。具体来说：
 *
 *		0 <= abs(time) < 24 hours
 *		0 <= abs(day)  < 30 days
 *
 *	此外，三个字段的符号位相同，因此要么
 *	三个字段都是负数，要么都是正数。
 */
Datum interval_justify_interval(PG_FUNCTION_ARGS)
{
	Interval   *fc_span = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_result;
	TimeOffset	fc_wholeday;
	int32		fc_wholemonth;

	fc_result = (Interval *) palloc(sizeof(Interval));
	fc_result->month = fc_span->month;
	fc_result->day = fc_span->day;
	fc_result->time = fc_span->time;

	/* 如果可能导致溢出，则预先调整天数 */
	if ((fc_result->day > 0 && fc_result->time > 0) ||
		(fc_result->day < 0 && fc_result->time < 0))
	{
		fc_wholemonth = fc_result->day / DAYS_PER_MONTH;
		fc_result->day -= fc_wholemonth * DAYS_PER_MONTH;
		if (pg_add_s32_overflow(fc_result->month, fc_wholemonth, &fc_result->month))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("interval out of range")));
	}

	/*
	 * 由于 TimeOffset 是 int64，abs(wholeday) 不能超过大约 1.07e8。如果
	 * 我们进行了预先调整，那么 abs(result->day) 小于 DAYS_PER_MONTH，因此
	 * 这个加法不可能溢出。如果我们没有预先调整，那么天和
	 * 时间是不同符号的，因此它仍然不可能溢出。
	 */
	TMODULO(fc_result->time, fc_wholeday, USECS_PER_DAY);
	fc_result->day += fc_wholeday;

	fc_wholemonth = fc_result->day / DAYS_PER_MONTH;
	fc_result->day -= fc_wholemonth * DAYS_PER_MONTH;
	if (pg_add_s32_overflow(fc_result->month, fc_wholemonth, &fc_result->month))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	if (fc_result->month > 0 &&
		(fc_result->day < 0 || (fc_result->day == 0 && fc_result->time < 0)))
	{
		fc_result->day += DAYS_PER_MONTH;
		fc_result->month--;
	}
	else if (fc_result->month < 0 &&
			 (fc_result->day > 0 || (fc_result->day == 0 && fc_result->time > 0)))
	{
		fc_result->day -= DAYS_PER_MONTH;
		fc_result->month++;
	}

	if (fc_result->day > 0 && fc_result->time < 0)
	{
		fc_result->time += USECS_PER_DAY;
		fc_result->day--;
	}
	else if (fc_result->day < 0 && fc_result->time > 0)
	{
		fc_result->time -= USECS_PER_DAY;
		fc_result->day++;
	}

	PG_RETURN_INTERVAL_P(fc_result);
}

/*
 *	interval_justify_hours()
 *
 *	调整间隔，使得“时间”包含少于一天的时间，将
 *	超出的部分加到“日”上。这在
 *	一些情况（例如非时区）中很有用，‘1 day’ = ‘24 hours’ 是有效的，
 *	例如间隔减法和除法。
 */
Datum interval_justify_hours(PG_FUNCTION_ARGS)
{
	Interval   *fc_span = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_result;
	TimeOffset	fc_wholeday;

	fc_result = (Interval *) palloc(sizeof(Interval));
	fc_result->month = fc_span->month;
	fc_result->day = fc_span->day;
	fc_result->time = fc_span->time;

	TMODULO(fc_result->time, fc_wholeday, USECS_PER_DAY);
	if (pg_add_s32_overflow(fc_result->day, fc_wholeday, &fc_result->day))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	if (fc_result->day > 0 && fc_result->time < 0)
	{
		fc_result->time += USECS_PER_DAY;
		fc_result->day--;
	}
	else if (fc_result->day < 0 && fc_result->time > 0)
	{
		fc_result->time -= USECS_PER_DAY;
		fc_result->day++;
	}

	PG_RETURN_INTERVAL_P(fc_result);
}

/*
 *	interval_justify_days()
 *
 *	调整间隔，使得“天”包含少于 30 天，将
 *	超出的部分加到“月”上。
 */
Datum interval_justify_days(PG_FUNCTION_ARGS)
{
	Interval   *fc_span = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_result;
	int32		fc_wholemonth;

	fc_result = (Interval *) palloc(sizeof(Interval));
	fc_result->month = fc_span->month;
	fc_result->day = fc_span->day;
	fc_result->time = fc_span->time;

	fc_wholemonth = fc_result->day / DAYS_PER_MONTH;
	fc_result->day -= fc_wholemonth * DAYS_PER_MONTH;
	if (pg_add_s32_overflow(fc_result->month, fc_wholemonth, &fc_result->month))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	if (fc_result->month > 0 && fc_result->day < 0)
	{
		fc_result->day += DAYS_PER_MONTH;
		fc_result->month--;
	}
	else if (fc_result->month < 0 && fc_result->day > 0)
	{
		fc_result->day -= DAYS_PER_MONTH;
		fc_result->month++;
	}

	PG_RETURN_INTERVAL_P(fc_result);
}

/* timestamp_pl_interval()
 * 将间隔添加到时间戳数据类型中。
 * 请注意，间隔包含定性年的 / 月和天
 * 单位的规定，因此尽量正确处理它们。
 * 要添加一个月，递增月份，并使用相同的日期。
 * 然后，如果下个月的天数更少，将日期
 * 设置为当月的最后一天。
 * 要添加一天，递增天数，并使用相同的时间。
 * 最后，添加“定量时间”。
 */
Datum timestamp_pl_interval(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(0);
	Interval   *fc_span = PG_GETARG_INTERVAL_P(1);
	Timestamp	fc_result;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		fc_result = fc_timestamp;
	else
	{
		if (fc_span->month != 0)
		{
			struct pg_tm fc_tt,
					   *fc_tm = &fc_tt;
			fsec_t		fc_fsec;

			if (timestamp2tm(fc_timestamp, NULL, fc_tm, &fc_fsec, NULL, NULL) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));

			if (pg_add_s32_overflow(fc_tm->tm_mon, fc_span->month, &fc_tm->tm_mon))
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
			if (fc_tm->tm_mon > MONTHS_PER_YEAR)
			{
				fc_tm->tm_year += (fc_tm->tm_mon - 1) / MONTHS_PER_YEAR;
				fc_tm->tm_mon = ((fc_tm->tm_mon - 1) % MONTHS_PER_YEAR) + 1;
			}
			else if (fc_tm->tm_mon < 1)
			{
				fc_tm->tm_year += fc_tm->tm_mon / MONTHS_PER_YEAR - 1;
				fc_tm->tm_mon = fc_tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR;
			}

			/* 调整月底边界问题... */
			if (fc_tm->tm_mday > day_tab[isleap(fc_tm->tm_year)][fc_tm->tm_mon - 1])
				fc_tm->tm_mday = (day_tab[isleap(fc_tm->tm_year)][fc_tm->tm_mon - 1]);

			if (tm2timestamp(fc_tm, fc_fsec, NULL, &fc_timestamp) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
		}

		if (fc_span->day != 0)
		{
			struct pg_tm fc_tt,
					   *fc_tm = &fc_tt;
			fsec_t		fc_fsec;
			int			fc_julian;

			if (timestamp2tm(fc_timestamp, NULL, fc_tm, &fc_fsec, NULL, NULL) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));

			/*
			 * 通过转换到朱里安日和从朱里安日转换来添加天数。我们在这里需要一个溢出
			 * 检查，因为 j2date 期望一个非负整数输入。
			 */
			fc_julian = date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
			if (pg_add_s32_overflow(fc_julian, fc_span->day, &fc_julian) ||
				fc_julian < 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
			j2date(fc_julian, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);

			if (tm2timestamp(fc_tm, fc_fsec, NULL, &fc_timestamp) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
		}

		if (pg_add_s64_overflow(fc_timestamp, fc_span->time, &fc_timestamp))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		if (!IS_VALID_TIMESTAMP(fc_timestamp))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		fc_result = fc_timestamp;
	}

	PG_RETURN_TIMESTAMP(fc_result);
}

Datum timestamp_mi_interval(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(0);
	Interval   *fc_span = PG_GETARG_INTERVAL_P(1);
	Interval	fc_tspan;

	fc_tspan.month = -fc_span->month;
	fc_tspan.day = -fc_span->day;
	fc_tspan.time = -fc_span->time;

	return DirectFunctionCall2(timestamp_pl_interval,
							   TimestampGetDatum(fc_timestamp),
							   PointerGetDatum(&fc_tspan));
}


/* timestamptz_pl_interval()
 * 将间隔添加到带有时区的时间戳数据类型中。
 * 请注意，间隔包含定性年的 / 月
 * 单位的规定，因此尽量正确处理它们。
 * 要添加一个月，递增月份，并使用相同的日期。
 * 然后，如果下个月的天数更少，将日期
 * 设置为当月的最后一天。
 * 最后，添加“定量时间”。
 */
Datum timestamptz_pl_interval(PG_FUNCTION_ARGS)
{
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(0);
	Interval   *fc_span = PG_GETARG_INTERVAL_P(1);
	TimestampTz fc_result;
	int			fc_tz;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		fc_result = fc_timestamp;
	else
	{
		if (fc_span->month != 0)
		{
			struct pg_tm fc_tt,
					   *fc_tm = &fc_tt;
			fsec_t		fc_fsec;

			if (timestamp2tm(fc_timestamp, &fc_tz, fc_tm, &fc_fsec, NULL, NULL) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));

			if (pg_add_s32_overflow(fc_tm->tm_mon, fc_span->month, &fc_tm->tm_mon))
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
			if (fc_tm->tm_mon > MONTHS_PER_YEAR)
			{
				fc_tm->tm_year += (fc_tm->tm_mon - 1) / MONTHS_PER_YEAR;
				fc_tm->tm_mon = ((fc_tm->tm_mon - 1) % MONTHS_PER_YEAR) + 1;
			}
			else if (fc_tm->tm_mon < 1)
			{
				fc_tm->tm_year += fc_tm->tm_mon / MONTHS_PER_YEAR - 1;
				fc_tm->tm_mon = fc_tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR;
			}

			/* 调整月底边界问题... */
			if (fc_tm->tm_mday > day_tab[isleap(fc_tm->tm_year)][fc_tm->tm_mon - 1])
				fc_tm->tm_mday = (day_tab[isleap(fc_tm->tm_year)][fc_tm->tm_mon - 1]);

			fc_tz = DetermineTimeZoneOffset(fc_tm, session_timezone);

			if (tm2timestamp(fc_tm, fc_fsec, &fc_tz, &fc_timestamp) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
		}

		if (fc_span->day != 0)
		{
			struct pg_tm fc_tt,
					   *fc_tm = &fc_tt;
			fsec_t		fc_fsec;
			int			fc_julian;

			if (timestamp2tm(fc_timestamp, &fc_tz, fc_tm, &fc_fsec, NULL, NULL) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));

			/*
			 * 通过转换到朱里安日和从朱里安日转换来添加天数。我们在这里需要一个溢出
			 * 检查，因为 j2date 期望一个非负整数输入。
			 * 实际上，对于小的负朱里安日期，它将返回正确的答案；我们应该允许 -1 以避免
			 * 依赖时区的故障，如在 timestamp.h 中讨论的那样。
			 */
			fc_julian = date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
			if (pg_add_s32_overflow(fc_julian, fc_span->day, &fc_julian) ||
				fc_julian < -1)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
			j2date(fc_julian, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);

			fc_tz = DetermineTimeZoneOffset(fc_tm, session_timezone);

			if (tm2timestamp(fc_tm, fc_fsec, &fc_tz, &fc_timestamp) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
		}

		if (pg_add_s64_overflow(fc_timestamp, fc_span->time, &fc_timestamp))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		if (!IS_VALID_TIMESTAMP(fc_timestamp))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		fc_result = fc_timestamp;
	}

	PG_RETURN_TIMESTAMP(fc_result);
}

Datum timestamptz_mi_interval(PG_FUNCTION_ARGS)
{
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(0);
	Interval   *fc_span = PG_GETARG_INTERVAL_P(1);
	Interval	fc_tspan;

	fc_tspan.month = -fc_span->month;
	fc_tspan.day = -fc_span->day;
	fc_tspan.time = -fc_span->time;

	return DirectFunctionCall2(timestamptz_pl_interval,
							   TimestampGetDatum(fc_timestamp),
							   PointerGetDatum(&fc_tspan));
}


Datum interval_um(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_result;

	fc_result = (Interval *) palloc(sizeof(Interval));

	fc_result->time = -fc_interval->time;
	/* 从 int4um 复制的溢出检查 */
	if (fc_interval->time != 0 && SAMESIGN(fc_result->time, fc_interval->time))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));
	fc_result->day = -fc_interval->day;
	if (fc_interval->day != 0 && SAMESIGN(fc_result->day, fc_interval->day))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));
	fc_result->month = -fc_interval->month;
	if (fc_interval->month != 0 && SAMESIGN(fc_result->month, fc_interval->month))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	PG_RETURN_INTERVAL_P(fc_result);
}


Datum interval_smaller(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);
	Interval   *fc_result;

	/* 使用 interval_cmp_internal 确保这与比较一致 */
	if (fc_interval_cmp_internal(fc_interval1, fc_interval2) < 0)
		fc_result = fc_interval1;
	else
		fc_result = fc_interval2;
	PG_RETURN_INTERVAL_P(fc_result);
}

Datum interval_larger(PG_FUNCTION_ARGS)
{
	Interval   *fc_interval1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_interval2 = PG_GETARG_INTERVAL_P(1);
	Interval   *fc_result;

	if (fc_interval_cmp_internal(fc_interval1, fc_interval2) > 0)
		fc_result = fc_interval1;
	else
		fc_result = fc_interval2;
	PG_RETURN_INTERVAL_P(fc_result);
}

Datum interval_pl(PG_FUNCTION_ARGS)
{
	Interval   *fc_span1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_span2 = PG_GETARG_INTERVAL_P(1);
	Interval   *fc_result;

	fc_result = (Interval *) palloc(sizeof(Interval));

	fc_result->month = fc_span1->month + fc_span2->month;
	/* 从 int4pl 复制的溢出检查 */
	if (SAMESIGN(fc_span1->month, fc_span2->month) &&
		!SAMESIGN(fc_result->month, fc_span1->month))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	fc_result->day = fc_span1->day + fc_span2->day;
	if (SAMESIGN(fc_span1->day, fc_span2->day) &&
		!SAMESIGN(fc_result->day, fc_span1->day))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	fc_result->time = fc_span1->time + fc_span2->time;
	if (SAMESIGN(fc_span1->time, fc_span2->time) &&
		!SAMESIGN(fc_result->time, fc_span1->time))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	PG_RETURN_INTERVAL_P(fc_result);
}

Datum interval_mi(PG_FUNCTION_ARGS)
{
	Interval   *fc_span1 = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_span2 = PG_GETARG_INTERVAL_P(1);
	Interval   *fc_result;

	fc_result = (Interval *) palloc(sizeof(Interval));

	fc_result->month = fc_span1->month - fc_span2->month;
	/* 从 int4mi 复制的溢出检查 */
	if (!SAMESIGN(fc_span1->month, fc_span2->month) &&
		!SAMESIGN(fc_result->month, fc_span1->month))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	fc_result->day = fc_span1->day - fc_span2->day;
	if (!SAMESIGN(fc_span1->day, fc_span2->day) &&
		!SAMESIGN(fc_result->day, fc_span1->day))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	fc_result->time = fc_span1->time - fc_span2->time;
	if (!SAMESIGN(fc_span1->time, fc_span2->time) &&
		!SAMESIGN(fc_result->time, fc_span1->time))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	PG_RETURN_INTERVAL_P(fc_result);
}

/*
 *	没有 interval_abs()：返回什么值不明确：
 *	  http://archives.postgresql.org/pgsql-general/2009-10/msg01031.php
 *	  http://archives.postgresql.org/pgsql-general/2009-11/msg00041.php
 */

Datum interval_mul(PG_FUNCTION_ARGS)
{
	Interval   *fc_span = PG_GETARG_INTERVAL_P(0);
	float8		fc_factor = PG_GETARG_FLOAT8(1);
	double		fc_month_remainder_days,
				fc_sec_remainder,
				fc_result_double;
	int32		fc_orig_month = fc_span->month,
				fc_orig_day = fc_span->day;
	Interval   *fc_result;

	fc_result = (Interval *) palloc(sizeof(Interval));

	fc_result_double = fc_span->month * fc_factor;
	if (isnan(fc_result_double) || !FLOAT8_FITS_IN_INT32(fc_result_double))
		goto out_of_range;
	fc_result->month = (int32) fc_result_double;

	fc_result_double = fc_span->day * fc_factor;
	if (isnan(fc_result_double) || !FLOAT8_FITS_IN_INT32(fc_result_double))
		goto out_of_range;
	fc_result->day = (int32) fc_result_double;

	/*
	 * 上面的代码正确处理了月份和天数乘积的整数部分，
	 * 但我们必须处理因因子为非整数而产生的
	 * 任何小数部分。我们使用转换因子 DAYS_PER_MONTH 和
	 * SECS_PER_DAY 将分数级联到较低的单位。请注意，我们并不会向上级联，因为表示法
	 * 并没有强制我们这样做。用户可以选择稍后使用
	 * justify_hours 和 / 或 justify_days 向上级联。
	 */

	/*
	 * 将小数月份的满天数转换为天数。
	 *
	 * 浮点计算本质上是不精确的，因此这些
	 * 计算的设计目的是产生尽可能可靠的结果。
	 * TSROUND() 是所需的，以便在
	 * 合适的情况下更准确地产生整数。
	 */
	fc_month_remainder_days = (fc_orig_month * fc_factor - fc_result->month) * DAYS_PER_MONTH;
	fc_month_remainder_days = TSROUND(fc_month_remainder_days);
	fc_sec_remainder = (fc_orig_day * fc_factor - fc_result->day +
					 fc_month_remainder_days - (int) fc_month_remainder_days) * SECS_PER_DAY;
	fc_sec_remainder = TSROUND(fc_sec_remainder);

	/*
	 * 由于四舍五入，可能会有 24:00:00 小时，或因为时间
	 * 从月份和天数级联而大于 24 小时。如果
	 * 级联和秒因子运算的组合本身可能仍然大于 24。
	 */
	if (Abs(fc_sec_remainder) >= SECS_PER_DAY)
	{
		if (pg_add_s32_overflow(fc_result->day,
								(int) (fc_sec_remainder / SECS_PER_DAY),
								&fc_result->day))
			goto out_of_range;
		fc_sec_remainder -= (int) (fc_sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
	}

	/* 向下级联单位 */
	if (pg_add_s32_overflow(fc_result->day, (int32) fc_month_remainder_days,
							&fc_result->day))
		goto out_of_range;
	fc_result_double = rint(fc_span->time * fc_factor + fc_sec_remainder * USECS_PER_SEC);
	if (isnan(fc_result_double) || !FLOAT8_FITS_IN_INT64(fc_result_double))
		goto out_of_range;
	fc_result->time = (int64) fc_result_double;

	PG_RETURN_INTERVAL_P(fc_result);

out_of_range:
	ereport(ERROR,
			errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
			errmsg("interval out of range"));

	PG_RETURN_NULL();			/* 保持编译器安静 */
}

Datum mul_d_interval(PG_FUNCTION_ARGS)
{
	/* 参数为 float8 和 Interval *，但保持为通用 Datum */
	Datum		fc_factor = PG_GETARG_DATUM(0);
	Datum		fc_span = PG_GETARG_DATUM(1);

	return DirectFunctionCall2(interval_mul, fc_span, fc_factor);
}

Datum interval_div(PG_FUNCTION_ARGS)
{
	Interval   *fc_span = PG_GETARG_INTERVAL_P(0);
	float8		fc_factor = PG_GETARG_FLOAT8(1);
	double		fc_month_remainder_days,
				fc_sec_remainder,
				fc_result_double;
	int32		fc_orig_month = fc_span->month,
				fc_orig_day = fc_span->day;
	Interval   *fc_result;

	fc_result = (Interval *) palloc(sizeof(Interval));

	if (fc_factor == 0.0)
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));

	fc_result_double = fc_span->month / fc_factor;
	if (isnan(fc_result_double) || !FLOAT8_FITS_IN_INT32(fc_result_double))
		goto out_of_range;
	fc_result->month = (int32) fc_result_double;

	fc_result_double = fc_span->day / fc_factor;
	if (isnan(fc_result_double) || !FLOAT8_FITS_IN_INT32(fc_result_double))
		goto out_of_range;
	fc_result->day = (int32) fc_result_double;

	/*
	 * 将小数月份的满天数转换为天数。请参见 interval_mul() 中的评论。
	 */
	fc_month_remainder_days = (fc_orig_month / fc_factor - fc_result->month) * DAYS_PER_MONTH;
	fc_month_remainder_days = TSROUND(fc_month_remainder_days);
	fc_sec_remainder = (fc_orig_day / fc_factor - fc_result->day +
					 fc_month_remainder_days - (int) fc_month_remainder_days) * SECS_PER_DAY;
	fc_sec_remainder = TSROUND(fc_sec_remainder);
	if (Abs(fc_sec_remainder) >= SECS_PER_DAY)
	{
		if (pg_add_s32_overflow(fc_result->day,
								(int) (fc_sec_remainder / SECS_PER_DAY),
								&fc_result->day))
			goto out_of_range;
		fc_sec_remainder -= (int) (fc_sec_remainder / SECS_PER_DAY) * SECS_PER_DAY;
	}

	/* 向下级联单位 */
	if (pg_add_s32_overflow(fc_result->day, (int32) fc_month_remainder_days,
							&fc_result->day))
		goto out_of_range;
	fc_result_double = rint(fc_span->time / fc_factor + fc_sec_remainder * USECS_PER_SEC);
	if (isnan(fc_result_double) || !FLOAT8_FITS_IN_INT64(fc_result_double))
		goto out_of_range;
	fc_result->time = (int64) fc_result_double;

	PG_RETURN_INTERVAL_P(fc_result);

out_of_range:
	ereport(ERROR,
			errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
			errmsg("interval out of range"));

	PG_RETURN_NULL();			/* 保持编译器安静 */
}



/********************************************************************************
 * in_range 支持时间戳和区间的函数。
 *
 * 根据 SQL 规范，我们将它们作为偏移类型的区间支持。
 * 规范中关于偏移不能为负的限制对区间来说有点难以解读，
 * 但我们选择将其解释为与我们的区间比较运算符相同。
 ********************************************************************************/

Datum in_range_timestamptz_interval(PG_FUNCTION_ARGS)
{
	TimestampTz fc_val = PG_GETARG_TIMESTAMPTZ(0);
	TimestampTz fc_base = PG_GETARG_TIMESTAMPTZ(1);
	Interval   *fc_offset = PG_GETARG_INTERVAL_P(2);
	bool		fc_sub = PG_GETARG_BOOL(3);
	bool		fc_less = PG_GETARG_BOOL(4);
	TimestampTz fc_sum;

	if (int128_compare(fc_interval_cmp_value(fc_offset), int64_to_int128(0)) < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
				 errmsg("invalid preceding or following size in window function")));

	/* 我们目前不在这里避免溢出风险 */
	if (fc_sub)
		fc_sum = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_mi_interval,
													  TimestampTzGetDatum(fc_base),
													  IntervalPGetDatum(fc_offset)));
	else
		fc_sum = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,
													  TimestampTzGetDatum(fc_base),
													  IntervalPGetDatum(fc_offset)));

	if (fc_less)
		PG_RETURN_BOOL(fc_val <= fc_sum);
	else
		PG_RETURN_BOOL(fc_val >= fc_sum);
}

Datum in_range_timestamp_interval(PG_FUNCTION_ARGS)
{
	Timestamp	fc_val = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_base = PG_GETARG_TIMESTAMP(1);
	Interval   *fc_offset = PG_GETARG_INTERVAL_P(2);
	bool		fc_sub = PG_GETARG_BOOL(3);
	bool		fc_less = PG_GETARG_BOOL(4);
	Timestamp	fc_sum;

	if (int128_compare(fc_interval_cmp_value(fc_offset), int64_to_int128(0)) < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
				 errmsg("invalid preceding or following size in window function")));

	/* 我们目前不在这里避免溢出风险 */
	if (fc_sub)
		fc_sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
													TimestampGetDatum(fc_base),
													IntervalPGetDatum(fc_offset)));
	else
		fc_sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_pl_interval,
													TimestampGetDatum(fc_base),
													IntervalPGetDatum(fc_offset)));

	if (fc_less)
		PG_RETURN_BOOL(fc_val <= fc_sum);
	else
		PG_RETURN_BOOL(fc_val >= fc_sum);
}

Datum in_range_interval_interval(PG_FUNCTION_ARGS)
{
	Interval   *fc_val = PG_GETARG_INTERVAL_P(0);
	Interval   *fc_base = PG_GETARG_INTERVAL_P(1);
	Interval   *fc_offset = PG_GETARG_INTERVAL_P(2);
	bool		fc_sub = PG_GETARG_BOOL(3);
	bool		fc_less = PG_GETARG_BOOL(4);
	Interval   *fc_sum;

	if (int128_compare(fc_interval_cmp_value(fc_offset), int64_to_int128(0)) < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
				 errmsg("invalid preceding or following size in window function")));

	/* 我们目前不在这里避免溢出风险 */
	if (fc_sub)
		fc_sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
													IntervalPGetDatum(fc_base),
													IntervalPGetDatum(fc_offset)));
	else
		fc_sum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
													IntervalPGetDatum(fc_base),
													IntervalPGetDatum(fc_offset)));

	if (fc_less)
		PG_RETURN_BOOL(fc_interval_cmp_internal(fc_val, fc_sum) <= 0);
	else
		PG_RETURN_BOOL(fc_interval_cmp_internal(fc_val, fc_sum) >= 0);
}


/*
 * interval_accum, interval_accum_inv 和 interval_avg 实现
 * AVG(interval) 聚合。
 *
 * 此聚合的过渡数据类型是一个包含两个元素的数组，
 * 其中第一个是运行总和，第二个包含目前在其 'time' 字段中的值的数量。
 * 这有点丑，但总比为此发明一个专用数据类型要好。
 */

Datum interval_accum(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_transarray = PG_GETARG_ARRAYTYPE_P(0);
	Interval   *fc_newval = PG_GETARG_INTERVAL_P(1);
	Datum	   *fc_transdatums;
	int			fc_ndatums;
	Interval	fc_sumX,
				fc_N;
	Interval   *fc_newsum;
	ArrayType  *fc_result;

	deconstruct_array(fc_transarray,
					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
					  &fc_transdatums, NULL, &fc_ndatums);
	if (fc_ndatums != 2)
		elog(ERROR, "expected 2-element interval array");

	fc_sumX = *(DatumGetIntervalP(fc_transdatums[0]));
	fc_N = *(DatumGetIntervalP(fc_transdatums[1]));

	fc_newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
												   IntervalPGetDatum(&fc_sumX),
												   IntervalPGetDatum(fc_newval)));
	fc_N.time += 1;

	fc_transdatums[0] = IntervalPGetDatum(fc_newsum);
	fc_transdatums[1] = IntervalPGetDatum(&fc_N);

	fc_result = construct_array(fc_transdatums, 2,
							 INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);

	PG_RETURN_ARRAYTYPE_P(fc_result);
}

Datum interval_combine(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_transarray1 = PG_GETARG_ARRAYTYPE_P(0);
	ArrayType  *fc_transarray2 = PG_GETARG_ARRAYTYPE_P(1);
	Datum	   *fc_transdatums1;
	Datum	   *fc_transdatums2;
	int			fc_ndatums1;
	int			fc_ndatums2;
	Interval	fc_sum1,
				fc_N1;
	Interval	fc_sum2,
				fc_N2;

	Interval   *fc_newsum;
	ArrayType  *fc_result;

	deconstruct_array(fc_transarray1,
					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
					  &fc_transdatums1, NULL, &fc_ndatums1);
	if (fc_ndatums1 != 2)
		elog(ERROR, "expected 2-element interval array");

	fc_sum1 = *(DatumGetIntervalP(fc_transdatums1[0]));
	fc_N1 = *(DatumGetIntervalP(fc_transdatums1[1]));

	deconstruct_array(fc_transarray2,
					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
					  &fc_transdatums2, NULL, &fc_ndatums2);
	if (fc_ndatums2 != 2)
		elog(ERROR, "expected 2-element interval array");

	fc_sum2 = *(DatumGetIntervalP(fc_transdatums2[0]));
	fc_N2 = *(DatumGetIntervalP(fc_transdatums2[1]));

	fc_newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
												   IntervalPGetDatum(&fc_sum1),
												   IntervalPGetDatum(&fc_sum2)));
	fc_N1.time += fc_N2.time;

	fc_transdatums1[0] = IntervalPGetDatum(fc_newsum);
	fc_transdatums1[1] = IntervalPGetDatum(&fc_N1);

	fc_result = construct_array(fc_transdatums1, 2,
							 INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);

	PG_RETURN_ARRAYTYPE_P(fc_result);
}

Datum interval_accum_inv(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_transarray = PG_GETARG_ARRAYTYPE_P(0);
	Interval   *fc_newval = PG_GETARG_INTERVAL_P(1);
	Datum	   *fc_transdatums;
	int			fc_ndatums;
	Interval	fc_sumX,
				fc_N;
	Interval   *fc_newsum;
	ArrayType  *fc_result;

	deconstruct_array(fc_transarray,
					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
					  &fc_transdatums, NULL, &fc_ndatums);
	if (fc_ndatums != 2)
		elog(ERROR, "expected 2-element interval array");

	fc_sumX = *(DatumGetIntervalP(fc_transdatums[0]));
	fc_N = *(DatumGetIntervalP(fc_transdatums[1]));

	fc_newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
												   IntervalPGetDatum(&fc_sumX),
												   IntervalPGetDatum(fc_newval)));
	fc_N.time -= 1;

	fc_transdatums[0] = IntervalPGetDatum(fc_newsum);
	fc_transdatums[1] = IntervalPGetDatum(&fc_N);

	fc_result = construct_array(fc_transdatums, 2,
							 INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);

	PG_RETURN_ARRAYTYPE_P(fc_result);
}

Datum interval_avg(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_transarray = PG_GETARG_ARRAYTYPE_P(0);
	Datum	   *fc_transdatums;
	int			fc_ndatums;
	Interval	fc_sumX,
				fc_N;

	deconstruct_array(fc_transarray,
					  INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
					  &fc_transdatums, NULL, &fc_ndatums);
	if (fc_ndatums != 2)
		elog(ERROR, "expected 2-element interval array");

	fc_sumX = *(DatumGetIntervalP(fc_transdatums[0]));
	fc_N = *(DatumGetIntervalP(fc_transdatums[1]));

	/* SQL 定义零值的 AVG 为 NULL */
	if (fc_N.time == 0)
		PG_RETURN_NULL();

	return DirectFunctionCall2(interval_div,
							   IntervalPGetDatum(&fc_sumX),
							   Float8GetDatum((double) fc_N.time));
}


/* timestamp_age()
 * 计算时间差，同时保留年/月字段。
 * 注意，这不会导致准确的绝对时间跨度，
 * 因为一旦算术运算完成，年和月就失去了上下文。
 */
Datum timestamp_age(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt1 = PG_GETARG_TIMESTAMP(0);
	Timestamp	fc_dt2 = PG_GETARG_TIMESTAMP(1);
	Interval   *fc_result;
	fsec_t		fc_fsec1,
				fc_fsec2;
	struct pg_itm fc_tt,
			   *fc_tm = &fc_tt;
	struct pg_tm fc_tt1,
			   *fc_tm1 = &fc_tt1;
	struct pg_tm fc_tt2,
			   *fc_tm2 = &fc_tt2;

	fc_result = (Interval *) palloc(sizeof(Interval));

	if (timestamp2tm(fc_dt1, NULL, fc_tm1, &fc_fsec1, NULL, NULL) == 0 &&
		timestamp2tm(fc_dt2, NULL, fc_tm2, &fc_fsec2, NULL, NULL) == 0)
	{
		/* 形成符号差 */
		fc_tm->tm_usec = fc_fsec1 - fc_fsec2;
		fc_tm->tm_sec = fc_tm1->tm_sec - fc_tm2->tm_sec;
		fc_tm->tm_min = fc_tm1->tm_min - fc_tm2->tm_min;
		fc_tm->tm_hour = fc_tm1->tm_hour - fc_tm2->tm_hour;
		fc_tm->tm_mday = fc_tm1->tm_mday - fc_tm2->tm_mday;
		fc_tm->tm_mon = fc_tm1->tm_mon - fc_tm2->tm_mon;
		fc_tm->tm_year = fc_tm1->tm_year - fc_tm2->tm_year;

		/* 如有必要翻转符号... */
		if (fc_dt1 < fc_dt2)
		{
			fc_tm->tm_usec = -fc_tm->tm_usec;
			fc_tm->tm_sec = -fc_tm->tm_sec;
			fc_tm->tm_min = -fc_tm->tm_min;
			fc_tm->tm_hour = -fc_tm->tm_hour;
			fc_tm->tm_mday = -fc_tm->tm_mday;
			fc_tm->tm_mon = -fc_tm->tm_mon;
			fc_tm->tm_year = -fc_tm->tm_year;
		}

		/* 将任何负字段传播到下一个更高字段 */
		while (fc_tm->tm_usec < 0)
		{
			fc_tm->tm_usec += USECS_PER_SEC;
			fc_tm->tm_sec--;
		}

		while (fc_tm->tm_sec < 0)
		{
			fc_tm->tm_sec += SECS_PER_MINUTE;
			fc_tm->tm_min--;
		}

		while (fc_tm->tm_min < 0)
		{
			fc_tm->tm_min += MINS_PER_HOUR;
			fc_tm->tm_hour--;
		}

		while (fc_tm->tm_hour < 0)
		{
			fc_tm->tm_hour += HOURS_PER_DAY;
			fc_tm->tm_mday--;
		}

		while (fc_tm->tm_mday < 0)
		{
			if (fc_dt1 < fc_dt2)
			{
				fc_tm->tm_mday += day_tab[isleap(fc_tm1->tm_year)][fc_tm1->tm_mon - 1];
				fc_tm->tm_mon--;
			}
			else
			{
				fc_tm->tm_mday += day_tab[isleap(fc_tm2->tm_year)][fc_tm2->tm_mon - 1];
				fc_tm->tm_mon--;
			}
		}

		while (fc_tm->tm_mon < 0)
		{
			fc_tm->tm_mon += MONTHS_PER_YEAR;
			fc_tm->tm_year--;
		}

		/* 如有必要恢复符号... */
		if (fc_dt1 < fc_dt2)
		{
			fc_tm->tm_usec = -fc_tm->tm_usec;
			fc_tm->tm_sec = -fc_tm->tm_sec;
			fc_tm->tm_min = -fc_tm->tm_min;
			fc_tm->tm_hour = -fc_tm->tm_hour;
			fc_tm->tm_mday = -fc_tm->tm_mday;
			fc_tm->tm_mon = -fc_tm->tm_mon;
			fc_tm->tm_year = -fc_tm->tm_year;
		}

		if (itm2interval(fc_tm, fc_result) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("interval out of range")));
	}
	else
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	PG_RETURN_INTERVAL_P(fc_result);
}


/* timestamptz_age()
 * 计算时间差，同时保留年/月字段。
 * 注意，这不会导致准确的绝对时间跨度，
 * 因为一旦算术运算完成，年和月就失去了上下文。
 */
Datum timestamptz_age(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt1 = PG_GETARG_TIMESTAMPTZ(0);
	TimestampTz fc_dt2 = PG_GETARG_TIMESTAMPTZ(1);
	Interval   *fc_result;
	fsec_t		fc_fsec1,
				fc_fsec2;
	struct pg_itm fc_tt,
			   *fc_tm = &fc_tt;
	struct pg_tm fc_tt1,
			   *fc_tm1 = &fc_tt1;
	struct pg_tm fc_tt2,
			   *fc_tm2 = &fc_tt2;
	int			fc_tz1;
	int			fc_tz2;

	fc_result = (Interval *) palloc(sizeof(Interval));

	if (timestamp2tm(fc_dt1, &fc_tz1, fc_tm1, &fc_fsec1, NULL, NULL) == 0 &&
		timestamp2tm(fc_dt2, &fc_tz2, fc_tm2, &fc_fsec2, NULL, NULL) == 0)
	{
		/* 形成符号差 */
		fc_tm->tm_usec = fc_fsec1 - fc_fsec2;
		fc_tm->tm_sec = fc_tm1->tm_sec - fc_tm2->tm_sec;
		fc_tm->tm_min = fc_tm1->tm_min - fc_tm2->tm_min;
		fc_tm->tm_hour = fc_tm1->tm_hour - fc_tm2->tm_hour;
		fc_tm->tm_mday = fc_tm1->tm_mday - fc_tm2->tm_mday;
		fc_tm->tm_mon = fc_tm1->tm_mon - fc_tm2->tm_mon;
		fc_tm->tm_year = fc_tm1->tm_year - fc_tm2->tm_year;

		/* 如有必要翻转符号... */
		if (fc_dt1 < fc_dt2)
		{
			fc_tm->tm_usec = -fc_tm->tm_usec;
			fc_tm->tm_sec = -fc_tm->tm_sec;
			fc_tm->tm_min = -fc_tm->tm_min;
			fc_tm->tm_hour = -fc_tm->tm_hour;
			fc_tm->tm_mday = -fc_tm->tm_mday;
			fc_tm->tm_mon = -fc_tm->tm_mon;
			fc_tm->tm_year = -fc_tm->tm_year;
		}

		/* 将任何负字段传播到下一个更高字段 */
		while (fc_tm->tm_usec < 0)
		{
			fc_tm->tm_usec += USECS_PER_SEC;
			fc_tm->tm_sec--;
		}

		while (fc_tm->tm_sec < 0)
		{
			fc_tm->tm_sec += SECS_PER_MINUTE;
			fc_tm->tm_min--;
		}

		while (fc_tm->tm_min < 0)
		{
			fc_tm->tm_min += MINS_PER_HOUR;
			fc_tm->tm_hour--;
		}

		while (fc_tm->tm_hour < 0)
		{
			fc_tm->tm_hour += HOURS_PER_DAY;
			fc_tm->tm_mday--;
		}

		while (fc_tm->tm_mday < 0)
		{
			if (fc_dt1 < fc_dt2)
			{
				fc_tm->tm_mday += day_tab[isleap(fc_tm1->tm_year)][fc_tm1->tm_mon - 1];
				fc_tm->tm_mon--;
			}
			else
			{
				fc_tm->tm_mday += day_tab[isleap(fc_tm2->tm_year)][fc_tm2->tm_mon - 1];
				fc_tm->tm_mon--;
			}
		}

		while (fc_tm->tm_mon < 0)
		{
			fc_tm->tm_mon += MONTHS_PER_YEAR;
			fc_tm->tm_year--;
		}

		/*
		 * 注意：我们故意忽略 tz1 和 tz2 之间的任何差异。
		 */

		/* 如有必要恢复符号... */
		if (fc_dt1 < fc_dt2)
		{
			fc_tm->tm_usec = -fc_tm->tm_usec;
			fc_tm->tm_sec = -fc_tm->tm_sec;
			fc_tm->tm_min = -fc_tm->tm_min;
			fc_tm->tm_hour = -fc_tm->tm_hour;
			fc_tm->tm_mday = -fc_tm->tm_mday;
			fc_tm->tm_mon = -fc_tm->tm_mon;
			fc_tm->tm_year = -fc_tm->tm_year;
		}

		if (itm2interval(fc_tm, fc_result) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("interval out of range")));
	}
	else
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	PG_RETURN_INTERVAL_P(fc_result);
}


/*----------------------------------------------------------
 *	转换操作符。
 *---------------------------------------------------------*/


/* timestamp_bin()
 * 将时间戳按指定区间分 bin。
 */
Datum timestamp_bin(PG_FUNCTION_ARGS)
{
	Interval   *fc_stride = PG_GETARG_INTERVAL_P(0);
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(1);
	Timestamp	fc_origin = PG_GETARG_TIMESTAMP(2);
	Timestamp	fc_result,
				fc_stride_usecs,
				fc_tm_diff,
				fc_tm_modulo,
				fc_tm_delta;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMP(fc_timestamp);

	if (TIMESTAMP_NOT_FINITE(fc_origin))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("origin out of range")));

	if (fc_stride->month != 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("timestamps cannot be binned into intervals containing months or years")));

	if (unlikely(pg_mul_s64_overflow(fc_stride->day, USECS_PER_DAY, &fc_stride_usecs)) ||
		unlikely(pg_add_s64_overflow(fc_stride_usecs, fc_stride->time, &fc_stride_usecs)))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	if (fc_stride_usecs <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("stride must be greater than zero")));

	if (unlikely(pg_sub_s64_overflow(fc_timestamp, fc_origin, &fc_tm_diff)))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	/* 这些计算不能溢出 */
	fc_tm_modulo = fc_tm_diff % fc_stride_usecs;
	fc_tm_delta = fc_tm_diff - fc_tm_modulo;
	fc_result = fc_origin + fc_tm_delta;

	/*
	 * 当 tm_diff 是负数并且不是 stride_usecs 的倍数时，我们希望向 -infinity 而不是 0 取整。这个调整 *可能* 会导致溢出，
	 * 因为结果现在可能超出了 origin .. timestamp 的范围。
	 */
	if (fc_tm_modulo < 0)
	{
		if (unlikely(pg_sub_s64_overflow(fc_result, fc_stride_usecs, &fc_result)) ||
			!IS_VALID_TIMESTAMP(fc_result))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));
	}

	PG_RETURN_TIMESTAMP(fc_result);
}

/* timestamp_trunc()
 * 将时间戳截断到指定单位。
 */
Datum timestamp_trunc(PG_FUNCTION_ARGS)
{
	text	   *fc_units = PG_GETARG_TEXT_PP(0);
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(1);
	Timestamp	fc_result;
	int			fc_type,
				fc_val;
	char	   *fc_lowunits;
	fsec_t		fc_fsec;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMP(fc_timestamp);

	fc_lowunits = downcase_truncate_identifier(VARDATA_ANY(fc_units),
											VARSIZE_ANY_EXHDR(fc_units),
											false);

	fc_type = DecodeUnits(0, fc_lowunits, &fc_val);

	if (fc_type == UNITS)
	{
		if (timestamp2tm(fc_timestamp, NULL, fc_tm, &fc_fsec, NULL, NULL) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		switch (fc_val)
		{
			case DTK_WEEK:
				{
					int			fc_woy;

					fc_woy = date2isoweek(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);

					/*
					 * 如果是第 52/53 周且月份为 1 月，那么该周必须属于前一年。此外，一些
					 * 十二月的日期属于下一年。
					 */
					if (fc_woy >= 52 && fc_tm->tm_mon == 1)
						--fc_tm->tm_year;
					if (fc_woy <= 1 && fc_tm->tm_mon == MONTHS_PER_YEAR)
						++fc_tm->tm_year;
					isoweek2date(fc_woy, &(fc_tm->tm_year), &(fc_tm->tm_mon), &(fc_tm->tm_mday));
					fc_tm->tm_hour = 0;
					fc_tm->tm_min = 0;
					fc_tm->tm_sec = 0;
					fc_fsec = 0;
					break;
				}
			case DTK_MILLENNIUM:
				/* 请参阅 timestamptz_trunc 中的注释 */
				if (fc_tm->tm_year > 0)
					fc_tm->tm_year = ((fc_tm->tm_year + 999) / 1000) * 1000 - 999;
				else
					fc_tm->tm_year = -((999 - (fc_tm->tm_year - 1)) / 1000) * 1000 + 1;
				/* FALL THRU */
			case DTK_CENTURY:
				/* 请参阅 timestamptz_trunc 中的注释 */
				if (fc_tm->tm_year > 0)
					fc_tm->tm_year = ((fc_tm->tm_year + 99) / 100) * 100 - 99;
				else
					fc_tm->tm_year = -((99 - (fc_tm->tm_year - 1)) / 100) * 100 + 1;
				/* FALL THRU */
			case DTK_DECADE:
				/* 请参阅 timestamptz_trunc 中的注释 */
				if (fc_val != DTK_MILLENNIUM && fc_val != DTK_CENTURY)
				{
					if (fc_tm->tm_year > 0)
						fc_tm->tm_year = (fc_tm->tm_year / 10) * 10;
					else
						fc_tm->tm_year = -((8 - (fc_tm->tm_year - 1)) / 10) * 10;
				}
				/* FALL THRU */
			case DTK_YEAR:
				fc_tm->tm_mon = 1;
				/* FALL THRU */
			case DTK_QUARTER:
				fc_tm->tm_mon = (3 * ((fc_tm->tm_mon - 1) / 3)) + 1;
				/* FALL THRU */
			case DTK_MONTH:
				fc_tm->tm_mday = 1;
				/* FALL THRU */
			case DTK_DAY:
				fc_tm->tm_hour = 0;
				/* FALL THRU */
			case DTK_HOUR:
				fc_tm->tm_min = 0;
				/* FALL THRU */
			case DTK_MINUTE:
				fc_tm->tm_sec = 0;
				/* FALL THRU */
			case DTK_SECOND:
				fc_fsec = 0;
				break;

			case DTK_MILLISEC:
				fc_fsec = (fc_fsec / 1000) * 1000;
				break;

			case DTK_MICROSEC:
				break;

			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(TIMESTAMPOID))));
				fc_result = 0;
		}

		if (tm2timestamp(fc_tm, fc_fsec, NULL, &fc_result) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unit \"%s\" not recognized for type %s",
						fc_lowunits, format_type_be(TIMESTAMPOID))));
		fc_result = 0;
	}

	PG_RETURN_TIMESTAMP(fc_result);
}

/* timestamptz_bin()
 * 将 timestamptz 按指定区间分 bin，使用指定的起点。
 */
Datum timestamptz_bin(PG_FUNCTION_ARGS)
{
	Interval   *fc_stride = PG_GETARG_INTERVAL_P(0);
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(1);
	TimestampTz fc_origin = PG_GETARG_TIMESTAMPTZ(2);
	TimestampTz fc_result,
				fc_stride_usecs,
				fc_tm_diff,
				fc_tm_modulo,
				fc_tm_delta;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMPTZ(fc_timestamp);

	if (TIMESTAMP_NOT_FINITE(fc_origin))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("origin out of range")));

	if (fc_stride->month != 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("timestamps cannot be binned into intervals containing months or years")));

	if (unlikely(pg_mul_s64_overflow(fc_stride->day, USECS_PER_DAY, &fc_stride_usecs)) ||
		unlikely(pg_add_s64_overflow(fc_stride_usecs, fc_stride->time, &fc_stride_usecs)))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	if (fc_stride_usecs <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("stride must be greater than zero")));

	if (unlikely(pg_sub_s64_overflow(fc_timestamp, fc_origin, &fc_tm_diff)))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("interval out of range")));

	/* 这些计算不能溢出 */
	fc_tm_modulo = fc_tm_diff % fc_stride_usecs;
	fc_tm_delta = fc_tm_diff - fc_tm_modulo;
	fc_result = fc_origin + fc_tm_delta;

	/*
	 * 当 tm_diff 是负数并且不是 stride_usecs 的倍数时，我们希望向 -infinity 而不是 0 取整。这个调整 *可能* 会导致溢出，
	 * 因为结果现在可能超出了 origin .. timestamp 的范围。
	 */
	if (fc_tm_modulo < 0)
	{
		if (unlikely(pg_sub_s64_overflow(fc_result, fc_stride_usecs, &fc_result)) ||
			!IS_VALID_TIMESTAMP(fc_result))
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));
	}

	PG_RETURN_TIMESTAMPTZ(fc_result);
}

/*
 * timestamptz_trunc() 和 timestamptz_trunc_zone() 的通用代码。
 *
 * tzp 确定要截断的时区。我们假设无穷大的时间戳已经被拒绝。
 */
static TimestampTz fc_timestamptz_trunc_internal(text *fc_units, TimestampTz fc_timestamp, pg_tz *fc_tzp)
{
	TimestampTz fc_result;
	int			fc_tz;
	int			fc_type,
				fc_val;
	bool		fc_redotz = false;
	char	   *fc_lowunits;
	fsec_t		fc_fsec;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;

	fc_lowunits = downcase_truncate_identifier(VARDATA_ANY(fc_units),
											VARSIZE_ANY_EXHDR(fc_units),
											false);

	fc_type = DecodeUnits(0, fc_lowunits, &fc_val);

	if (fc_type == UNITS)
	{
		if (timestamp2tm(fc_timestamp, &fc_tz, fc_tm, &fc_fsec, NULL, fc_tzp) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		switch (fc_val)
		{
			case DTK_WEEK:
				{
					int			fc_woy;

					fc_woy = date2isoweek(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);

					/*
					 * 如果是第 52/53 周且月份为 1 月，那么该周必须属于前一年。此外，一些
					 * 十二月的日期属于下一年。
					 */
					if (fc_woy >= 52 && fc_tm->tm_mon == 1)
						--fc_tm->tm_year;
					if (fc_woy <= 1 && fc_tm->tm_mon == MONTHS_PER_YEAR)
						++fc_tm->tm_year;
					isoweek2date(fc_woy, &(fc_tm->tm_year), &(fc_tm->tm_mon), &(fc_tm->tm_mday));
					fc_tm->tm_hour = 0;
					fc_tm->tm_min = 0;
					fc_tm->tm_sec = 0;
					fc_fsec = 0;
					fc_redotz = true;
					break;
				}
				/* 可以考虑 DTK_THOUSAND 和 DTK_HUNDRED... */
			case DTK_MILLENNIUM:

				/*
				 * 截断到千年？这意味着什么？让我们设定千年的第一年... 即
				 * -1000, 1, 1001, 2001...
				 */
				if (fc_tm->tm_year > 0)
					fc_tm->tm_year = ((fc_tm->tm_year + 999) / 1000) * 1000 - 999;
				else
					fc_tm->tm_year = -((999 - (fc_tm->tm_year - 1)) / 1000) * 1000 + 1;
				/* FALL THRU */
			case DTK_CENTURY:
				/* 截断到世纪？如上：-100, 1, 101... */
				if (fc_tm->tm_year > 0)
					fc_tm->tm_year = ((fc_tm->tm_year + 99) / 100) * 100 - 99;
				else
					fc_tm->tm_year = -((99 - (fc_tm->tm_year - 1)) / 100) * 100 + 1;
				/* FALL THRU */
			case DTK_DECADE:

				/*
				 * 截断到十年？十年的第一年。若年份在之前已被截断，不能应用！
				 */
				if (fc_val != DTK_MILLENNIUM && fc_val != DTK_CENTURY)
				{
					if (fc_tm->tm_year > 0)
						fc_tm->tm_year = (fc_tm->tm_year / 10) * 10;
					else
						fc_tm->tm_year = -((8 - (fc_tm->tm_year - 1)) / 10) * 10;
				}
				/* FALL THRU */
			case DTK_YEAR:
				fc_tm->tm_mon = 1;
				/* FALL THRU */
			case DTK_QUARTER:
				fc_tm->tm_mon = (3 * ((fc_tm->tm_mon - 1) / 3)) + 1;
				/* FALL THRU */
			case DTK_MONTH:
				fc_tm->tm_mday = 1;
				/* FALL THRU */
			case DTK_DAY:
				fc_tm->tm_hour = 0;
				fc_redotz = true;	/* 所有大于等于 DAY 的情况 */
				/* FALL THRU */
			case DTK_HOUR:
				fc_tm->tm_min = 0;
				/* FALL THRU */
			case DTK_MINUTE:
				fc_tm->tm_sec = 0;
				/* FALL THRU */
			case DTK_SECOND:
				fc_fsec = 0;
				break;
			case DTK_MILLISEC:
				fc_fsec = (fc_fsec / 1000) * 1000;
				break;
			case DTK_MICROSEC:
				break;

			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(TIMESTAMPTZOID))));
				fc_result = 0;
		}

		if (fc_redotz)
			fc_tz = DetermineTimeZoneOffset(fc_tm, fc_tzp);

		if (tm2timestamp(fc_tm, fc_fsec, &fc_tz, &fc_result) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unit \"%s\" not recognized for type %s",
						fc_lowunits, format_type_be(TIMESTAMPTZOID))));
		fc_result = 0;
	}

	return fc_result;
}

/* timestamptz_trunc()
 * 将 timestamptz 截断到会话时区下的指定单位。
 */
Datum timestamptz_trunc(PG_FUNCTION_ARGS)
{
	text	   *fc_units = PG_GETARG_TEXT_PP(0);
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(1);
	TimestampTz fc_result;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMPTZ(fc_timestamp);

	fc_result = fc_timestamptz_trunc_internal(fc_units, fc_timestamp, session_timezone);

	PG_RETURN_TIMESTAMPTZ(fc_result);
}

/* timestamptz_trunc_zone()
 * 将 timestamptz 截断到指定时区下的指定单位。
 */
Datum timestamptz_trunc_zone(PG_FUNCTION_ARGS)
{
	text	   *fc_units = PG_GETARG_TEXT_PP(0);
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(1);
	text	   *fc_zone = PG_GETARG_TEXT_PP(2);
	TimestampTz fc_result;
	char		fc_tzname[TZ_STRLEN_MAX + 1];
	char	   *fc_lowzone;
	int			fc_type,
				fc_val;
	pg_tz	   *fc_tzp;

	/*
	 * timestamptz_zone() 在无穷大的输入下不查找时区，所以我们在这里也不这样做。
	 */
	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMP(fc_timestamp);

	/*
	 * 查找请求的时区（请参见 timestamptz_zone() 中的注释）。
	 */
	text_to_cstring_buffer(fc_zone, fc_tzname, sizeof(fc_tzname));

	/* DecodeTimezoneAbbrev需要小写输入 */
	fc_lowzone = downcase_truncate_identifier(fc_tzname,
										   strlen(fc_tzname),
										   false);

	fc_type = DecodeTimezoneAbbrev(0, fc_lowzone, &fc_val, &fc_tzp);

	if (fc_type == TZ || fc_type == DTZ)
	{
		/* 固定偏移缩写，获取 pg_tz 描述符 */
		fc_tzp = pg_tzset_offset(-fc_val);
	}
	else if (fc_type == DYNTZ)
	{
		/* 动态偏移缩写，使用其引用的时区 */
	}
	else
	{
		/* 尝试将其作为完整区域名称 */
		fc_tzp = pg_tzset(fc_tzname);
		if (!fc_tzp)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("time zone \"%s\" not recognized", fc_tzname)));
	}

	fc_result = fc_timestamptz_trunc_internal(fc_units, fc_timestamp, fc_tzp);

	PG_RETURN_TIMESTAMPTZ(fc_result);
}

/* interval_trunc()
 * 从区间中提取指定字段。
 */
Datum interval_trunc(PG_FUNCTION_ARGS)
{
	text	   *fc_units = PG_GETARG_TEXT_PP(0);
	Interval   *fc_interval = PG_GETARG_INTERVAL_P(1);
	Interval   *fc_result;
	int			fc_type,
				fc_val;
	char	   *fc_lowunits;
	struct pg_itm fc_tt,
			   *fc_tm = &fc_tt;

	fc_result = (Interval *) palloc(sizeof(Interval));

	fc_lowunits = downcase_truncate_identifier(VARDATA_ANY(fc_units),
											VARSIZE_ANY_EXHDR(fc_units),
											false);

	fc_type = DecodeUnits(0, fc_lowunits, &fc_val);

	if (fc_type == UNITS)
	{
		interval2itm(*fc_interval, fc_tm);
		switch (fc_val)
		{
			case DTK_MILLENNIUM:
				/* 注意：C 除法可能有负余数 */
				fc_tm->tm_year = (fc_tm->tm_year / 1000) * 1000;
				/* FALL THRU */
			case DTK_CENTURY:
				/* 注意：C 除法可能有负余数 */
				fc_tm->tm_year = (fc_tm->tm_year / 100) * 100;
				/* FALL THRU */
			case DTK_DECADE:
				/* 注意：C 除法可能有负余数 */
				fc_tm->tm_year = (fc_tm->tm_year / 10) * 10;
				/* FALL THRU */
			case DTK_YEAR:
				fc_tm->tm_mon = 0;
				/* FALL THRU */
			case DTK_QUARTER:
				fc_tm->tm_mon = 3 * (fc_tm->tm_mon / 3);
				/* FALL THRU */
			case DTK_MONTH:
				fc_tm->tm_mday = 0;
				/* FALL THRU */
			case DTK_DAY:
				fc_tm->tm_hour = 0;
				/* FALL THRU */
			case DTK_HOUR:
				fc_tm->tm_min = 0;
				/* FALL THRU */
			case DTK_MINUTE:
				fc_tm->tm_sec = 0;
				/* FALL THRU */
			case DTK_SECOND:
				fc_tm->tm_usec = 0;
				break;
			case DTK_MILLISEC:
				fc_tm->tm_usec = (fc_tm->tm_usec / 1000) * 1000;
				break;
			case DTK_MICROSEC:
				break;

			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(INTERVALOID)),
						 (fc_val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0));
		}

		if (itm2interval(fc_tm, fc_result) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("interval out of range")));
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unit \"%s\" not recognized for type %s",
						fc_lowunits, format_type_be(INTERVALOID))));
	}

	PG_RETURN_INTERVAL_P(fc_result);
}

/* isoweek2j()
 *
 * 返回与给定 ISO 8601 年和周的第一天（星期一）对应的 Julian 天。
 * Julian 日用于在 ISO 周日期和公历日期之间转换。
 */
int isoweek2j(int fc_year, int fc_week)
{
	int			fc_day0,
				fc_day4;

	/* 当前年份的第四天 */
	fc_day4 = date2j(fc_year, 1, 4);

	/* day0 == 到周第一天（星期一）的偏移量 */
	fc_day0 = j2day(fc_day4 - 1);

	return ((fc_week - 1) * 7) + (fc_day4 - fc_day0);
}

/* isoweek2date()
 * 将 ISO 年周数转换为日期。
 * 年份字段必须与 ISO 年一起指定！
 * karel 2000/08/07
 */
void isoweek2date(int fc_woy, int *fc_year, int *fc_mon, int *fc_mday)
{
	j2date(isoweek2j(*fc_year, fc_woy), fc_year, fc_mon, fc_mday);
}

/* isoweekdate2date()
 *
 * 将 ISO 8601 周日期（ISO 年，ISO 周）转换为公历日期。
 * 发送公历星期几，以便可以提供星期几字符串。
 * 用正确的公历值填充年、月和日。
 * 年必须作为 ISO 年传入。
 */
void isoweekdate2date(int fc_isoweek, int fc_wday, int *fc_year, int *fc_mon, int *fc_mday)
{
	int			fc_jday;

	fc_jday = isoweek2j(*fc_year, fc_isoweek);
	/* 将公历周开始（星期日=1）转换为 ISO 周开始（星期一=1） */
	if (fc_wday > 1)
		fc_jday += fc_wday - 2;
	else
		fc_jday += 6;
	j2date(fc_jday, fc_year, fc_mon, fc_mday);
}

/* date2isoweek()
 *
 * 返回 ISO 年周数。
 */
int date2isoweek(int fc_year, int fc_mon, int fc_mday)
{
	float8		fc_result;
	int			fc_day0,
				fc_day4,
				fc_dayn;

	/* 当前日期 */
	fc_dayn = date2j(fc_year, fc_mon, fc_mday);

	/* 当前年份的第四天 */
	fc_day4 = date2j(fc_year, 1, 4);

	/* day0 == 到周第一天（星期一）的偏移量 */
	fc_day0 = j2day(fc_day4 - 1);

	/*
	 * 我们需要包含星期四的第一周，否则这个日子会被算入前一年
	 * 用于计算周数
	 */
	if (fc_dayn < fc_day4 - fc_day0)
	{
		fc_day4 = date2j(fc_year - 1, 1, 4);

		/* day0 == 到周第一天（星期一）的偏移量 */
		fc_day0 = j2day(fc_day4 - 1);
	}

	fc_result = (fc_dayn - (fc_day4 - fc_day0)) / 7 + 1;

	/*
	 * 有时一年中的最后几天会落入下一年的第一周，所以需要对此进行检查。
	 */
	if (fc_result >= 52)
	{
		fc_day4 = date2j(fc_year + 1, 1, 4);

		/* day0 == 到周第一天（星期一）的偏移量 */
		fc_day0 = j2day(fc_day4 - 1);

		if (fc_dayn >= fc_day4 - fc_day0)
			fc_result = (fc_dayn - (fc_day4 - fc_day0)) / 7 + 1;
	}

	return (int) fc_result;
}


/* date2isoyear()
 *
 *	返回 ISO 8601 年号。
 *	注意：零或负数结果遵循“年零存在”的惯例。
 */
int date2isoyear(int fc_year, int fc_mon, int fc_mday)
{
	float8		fc_result;
	int			fc_day0,
				fc_day4,
				fc_dayn;

	/* 当前日期 */
	fc_dayn = date2j(fc_year, fc_mon, fc_mday);

	/* 当前年份的第四天 */
	fc_day4 = date2j(fc_year, 1, 4);

	/* day0 == 到周第一天（星期一）的偏移量 */
	fc_day0 = j2day(fc_day4 - 1);

	/*
	 * 我们需要包含星期四的第一周，否则这个日子会被算入前一年
	 * 用于计算周数
	 */
	if (fc_dayn < fc_day4 - fc_day0)
	{
		fc_day4 = date2j(fc_year - 1, 1, 4);

		/* day0 == 到周第一天（星期一）的偏移量 */
		fc_day0 = j2day(fc_day4 - 1);

		fc_year--;
	}

	fc_result = (fc_dayn - (fc_day4 - fc_day0)) / 7 + 1;

	/*
	 * 有时一年中的最后几天会落入下一年的第一周，所以需要对此进行检查。
	 */
	if (fc_result >= 52)
	{
		fc_day4 = date2j(fc_year + 1, 1, 4);

		/* day0 == 到周第一天（星期一）的偏移量 */
		fc_day0 = j2day(fc_day4 - 1);

		if (fc_dayn >= fc_day4 - fc_day0)
			fc_year++;
	}

	return fc_year;
}


/* date2isoyearday()
 *
 *	返回给定公历年、月份和日期的 ISO 8601 年中的某一天。
 *	可能的返回值为 1 到 371（在非闰年中为 364）。
 */
int date2isoyearday(int fc_year, int fc_mon, int fc_mday)
{
	return date2j(fc_year, fc_mon, fc_mday) - isoweek2j(date2isoyear(fc_year, fc_mon, fc_mday), 1) + 1;
}

/*
 * 非有限时间戳 Tz 部分
 *
 *	在从无限时间戳[tz]中提取时由 timestamp_part 和 timestamptz_part 使用。
 *	如果那是适当的结果，则返回 +/-Infinity，否则返回零（应理解为返回 NULL）。
 *
 *	在此抛出的无效单位错误应与调用函数中抛出的错误完全一致，否则有限和无限输入案例之间会出现意外的差异。
 */
static float8 fc_NonFiniteTimestampTzPart(int fc_type, int fc_unit, char *fc_lowunits,
						 bool fc_isNegative, bool fc_isTz)
{
	if ((fc_type != UNITS) && (fc_type != RESERV))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unit \"%s\" not recognized for type %s",
						fc_lowunits,
						format_type_be(fc_isTz ? TIMESTAMPTZOID : TIMESTAMPOID))));

	switch (fc_unit)
	{
			/* 振荡单位 */
		case DTK_MICROSEC:
		case DTK_MILLISEC:
		case DTK_SECOND:
		case DTK_MINUTE:
		case DTK_HOUR:
		case DTK_DAY:
		case DTK_MONTH:
		case DTK_QUARTER:
		case DTK_WEEK:
		case DTK_DOW:
		case DTK_ISODOW:
		case DTK_DOY:
		case DTK_TZ:
		case DTK_TZ_MINUTE:
		case DTK_TZ_HOUR:
			return 0.0;

			/* 单调递增单位 */
		case DTK_YEAR:
		case DTK_DECADE:
		case DTK_CENTURY:
		case DTK_MILLENNIUM:
		case DTK_JULIAN:
		case DTK_ISOYEAR:
		case DTK_EPOCH:
			if (fc_isNegative)
				return -get_float8_infinity();
			else
				return get_float8_infinity();

		default:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("unit \"%s\" not supported for type %s",
							fc_lowunits,
							format_type_be(fc_isTz ? TIMESTAMPTZOID : TIMESTAMPOID))));
			return 0.0;			/* 保持编译器安静 */
	}
}

/* timestamp_part() 和 extract_timestamp()
 * 从时间戳中提取指定字段。
 */
static Datum fc_timestamp_part_common(PG_FUNCTION_ARGS, bool fc_retnumeric)
{
	text	   *fc_units = PG_GETARG_TEXT_PP(0);
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(1);
	int64		fc_intresult;
	Timestamp	fc_epoch;
	int			fc_type,
				fc_val;
	char	   *fc_lowunits;
	fsec_t		fc_fsec;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;

	fc_lowunits = downcase_truncate_identifier(VARDATA_ANY(fc_units),
											VARSIZE_ANY_EXHDR(fc_units),
											false);

	fc_type = DecodeUnits(0, fc_lowunits, &fc_val);
	if (fc_type == UNKNOWN_FIELD)
		fc_type = DecodeSpecial(0, fc_lowunits, &fc_val);

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
	{
		double		fc_r = fc_NonFiniteTimestampTzPart(fc_type, fc_val, fc_lowunits,
												 TIMESTAMP_IS_NOBEGIN(fc_timestamp),
												 false);

		if (fc_r)
		{
			if (fc_retnumeric)
			{
				if (fc_r < 0)
					return DirectFunctionCall3(numeric_in,
											   CStringGetDatum("-Infinity"),
											   ObjectIdGetDatum(InvalidOid),
											   Int32GetDatum(-1));
				else if (fc_r > 0)
					return DirectFunctionCall3(numeric_in,
											   CStringGetDatum("Infinity"),
											   ObjectIdGetDatum(InvalidOid),
											   Int32GetDatum(-1));
			}
			else
				PG_RETURN_FLOAT8(fc_r);
		}
		else
			PG_RETURN_NULL();
	}

	if (fc_type == UNITS)
	{
		if (timestamp2tm(fc_timestamp, NULL, fc_tm, &fc_fsec, NULL, NULL) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		switch (fc_val)
		{
			case DTK_MICROSEC:
				fc_intresult = fc_tm->tm_sec * INT64CONST(1000000) + fc_fsec;
				break;

			case DTK_MILLISEC:
				if (fc_retnumeric)
					/*---
					 * tm->tm_sec * 1000 + fsec / 1000
					 * = (tm->tm_sec * 1'000'000 + fsec) / 1000
					 */
					PG_RETURN_NUMERIC(int64_div_fast_to_numeric(fc_tm->tm_sec * INT64CONST(1000000) + fc_fsec, 3));
				else
					PG_RETURN_FLOAT8(fc_tm->tm_sec * 1000.0 + fc_fsec / 1000.0);
				break;

			case DTK_SECOND:
				if (fc_retnumeric)
					/*---
					 * tm->tm_sec + fsec / 1'000'000
					 * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
					 */
					PG_RETURN_NUMERIC(int64_div_fast_to_numeric(fc_tm->tm_sec * INT64CONST(1000000) + fc_fsec, 6));
				else
					PG_RETURN_FLOAT8(fc_tm->tm_sec + fc_fsec / 1000000.0);
				break;

			case DTK_MINUTE:
				fc_intresult = fc_tm->tm_min;
				break;

			case DTK_HOUR:
				fc_intresult = fc_tm->tm_hour;
				break;

			case DTK_DAY:
				fc_intresult = fc_tm->tm_mday;
				break;

			case DTK_MONTH:
				fc_intresult = fc_tm->tm_mon;
				break;

			case DTK_QUARTER:
				fc_intresult = (fc_tm->tm_mon - 1) / 3 + 1;
				break;

			case DTK_WEEK:
				fc_intresult = date2isoweek(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
				break;

			case DTK_YEAR:
				if (fc_tm->tm_year > 0)
					fc_intresult = fc_tm->tm_year;
				else
					/* 没有年份0，只有1 BC和1 AD */
					fc_intresult = fc_tm->tm_year - 1;
				break;

			case DTK_DECADE:

				/*
				 * 关于日期，什么是十年？假设十年199
				 * 是 1990 到 1999... 十年 0 从公元前 1 年开始，-1
				 * 是公元前 11 年到公元前 2 年...
				 */
				if (fc_tm->tm_year >= 0)
					fc_intresult = fc_tm->tm_year / 10;
				else
					fc_intresult = -((8 - (fc_tm->tm_year - 1)) / 10);
				break;

			case DTK_CENTURY:

				/* ----
				 * 公元后的世纪，c>0：年份在 [ (c-1)* 100 + 1 : c*100 ]
				 * 公元前的世纪，c<0：年份在 [ c*100 : (c+1) * 100 - 1]
				 * 没有 0 世纪。
				 * ----
				 */
				if (fc_tm->tm_year > 0)
					fc_intresult = (fc_tm->tm_year + 99) / 100;
				else
					/* 注意：C 除法可能有负余数 */
					fc_intresult = -((99 - (fc_tm->tm_year - 1)) / 100);
				break;

			case DTK_MILLENNIUM:
				/* 参见上面的注释。 */
				if (fc_tm->tm_year > 0)
					fc_intresult = (fc_tm->tm_year + 999) / 1000;
				else
					fc_intresult = -((999 - (fc_tm->tm_year - 1)) / 1000);
				break;

			case DTK_JULIAN:
				if (fc_retnumeric)
					PG_RETURN_NUMERIC(numeric_add_opt_error(int64_to_numeric(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday)),
															numeric_div_opt_error(int64_to_numeric(((((fc_tm->tm_hour * MINS_PER_HOUR) + fc_tm->tm_min) * SECS_PER_MINUTE) + fc_tm->tm_sec) * INT64CONST(1000000) + fc_fsec),
																				  int64_to_numeric(SECS_PER_DAY * INT64CONST(1000000)),
																				  NULL),
															NULL));
				else
					PG_RETURN_FLOAT8(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday) +
									 ((((fc_tm->tm_hour * MINS_PER_HOUR) + fc_tm->tm_min) * SECS_PER_MINUTE) +
									  fc_tm->tm_sec + (fc_fsec / 1000000.0)) / (double) SECS_PER_DAY);
				break;

			case DTK_ISOYEAR:
				fc_intresult = date2isoyear(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
				/* 调整 BC 年 */
				if (fc_intresult <= 0)
					fc_intresult -= 1;
				break;

			case DTK_DOW:
			case DTK_ISODOW:
				fc_intresult = j2day(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday));
				if (fc_val == DTK_ISODOW && fc_intresult == 0)
					fc_intresult = 7;
				break;

			case DTK_DOY:
				fc_intresult = (date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday)
							 - date2j(fc_tm->tm_year, 1, 1) + 1);
				break;

			case DTK_TZ:
			case DTK_TZ_MINUTE:
			case DTK_TZ_HOUR:
			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(TIMESTAMPOID))));
				fc_intresult = 0;
		}
	}
	else if (fc_type == RESERV)
	{
		switch (fc_val)
		{
			case DTK_EPOCH:
				fc_epoch = SetEpochTimestamp();
				/* (timestamp - epoch) / 1000000 */
				if (fc_retnumeric)
				{
					Numeric		fc_result;

					if (fc_timestamp < (PG_INT64_MAX + fc_epoch))
						fc_result = int64_div_fast_to_numeric(fc_timestamp - fc_epoch, 6);
					else
					{
						fc_result = numeric_div_opt_error(numeric_sub_opt_error(int64_to_numeric(fc_timestamp),
																			 int64_to_numeric(fc_epoch),
																			 NULL),
													   int64_to_numeric(1000000),
													   NULL);
						fc_result = DatumGetNumeric(DirectFunctionCall2(numeric_round,
																	 NumericGetDatum(fc_result),
																	 Int32GetDatum(6)));
					}
					PG_RETURN_NUMERIC(fc_result);
				}
				else
				{
					float8		fc_result;

					/* 尝试避免减法中的精度损失 */
					if (fc_timestamp < (PG_INT64_MAX + fc_epoch))
						fc_result = (fc_timestamp - fc_epoch) / 1000000.0;
					else
						fc_result = ((float8) fc_timestamp - fc_epoch) / 1000000.0;
					PG_RETURN_FLOAT8(fc_result);
				}
				break;

			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(TIMESTAMPOID))));
				fc_intresult = 0;
		}
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unit \"%s\" not recognized for type %s",
						fc_lowunits, format_type_be(TIMESTAMPOID))));
		fc_intresult = 0;
	}

	if (fc_retnumeric)
		PG_RETURN_NUMERIC(int64_to_numeric(fc_intresult));
	else
		PG_RETURN_FLOAT8(fc_intresult);
}

Datum timestamp_part(PG_FUNCTION_ARGS)
{
	return fc_timestamp_part_common(fcinfo, false);
}

Datum extract_timestamp(PG_FUNCTION_ARGS)
{
	return fc_timestamp_part_common(fcinfo, true);
}

/* timestamptz_part() 和 extract_timestamptz()
 * 从带时区的时间戳中提取指定字段。
 */
static Datum fc_timestamptz_part_common(PG_FUNCTION_ARGS, bool fc_retnumeric)
{
	text	   *fc_units = PG_GETARG_TEXT_PP(0);
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(1);
	int64		fc_intresult;
	Timestamp	fc_epoch;
	int			fc_tz;
	int			fc_type,
				fc_val;
	char	   *fc_lowunits;
	fsec_t		fc_fsec;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;

	fc_lowunits = downcase_truncate_identifier(VARDATA_ANY(fc_units),
											VARSIZE_ANY_EXHDR(fc_units),
											false);

	fc_type = DecodeUnits(0, fc_lowunits, &fc_val);
	if (fc_type == UNKNOWN_FIELD)
		fc_type = DecodeSpecial(0, fc_lowunits, &fc_val);

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
	{
		double		fc_r = fc_NonFiniteTimestampTzPart(fc_type, fc_val, fc_lowunits,
												 TIMESTAMP_IS_NOBEGIN(fc_timestamp),
												 true);

		if (fc_r)
		{
			if (fc_retnumeric)
			{
				if (fc_r < 0)
					return DirectFunctionCall3(numeric_in,
											   CStringGetDatum("-Infinity"),
											   ObjectIdGetDatum(InvalidOid),
											   Int32GetDatum(-1));
				else if (fc_r > 0)
					return DirectFunctionCall3(numeric_in,
											   CStringGetDatum("Infinity"),
											   ObjectIdGetDatum(InvalidOid),
											   Int32GetDatum(-1));
			}
			else
				PG_RETURN_FLOAT8(fc_r);
		}
		else
			PG_RETURN_NULL();
	}

	if (fc_type == UNITS)
	{
		if (timestamp2tm(fc_timestamp, &fc_tz, fc_tm, &fc_fsec, NULL, NULL) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		switch (fc_val)
		{
			case DTK_TZ:
				fc_intresult = -fc_tz;
				break;

			case DTK_TZ_MINUTE:
				fc_intresult = (-fc_tz / SECS_PER_MINUTE) % MINS_PER_HOUR;
				break;

			case DTK_TZ_HOUR:
				fc_intresult = -fc_tz / SECS_PER_HOUR;
				break;

			case DTK_MICROSEC:
				fc_intresult = fc_tm->tm_sec * INT64CONST(1000000) + fc_fsec;
				break;

			case DTK_MILLISEC:
				if (fc_retnumeric)
					/*---
					 * tm->tm_sec * 1000 + fsec / 1000
					 * = (tm->tm_sec * 1'000'000 + fsec) / 1000
					 */
					PG_RETURN_NUMERIC(int64_div_fast_to_numeric(fc_tm->tm_sec * INT64CONST(1000000) + fc_fsec, 3));
				else
					PG_RETURN_FLOAT8(fc_tm->tm_sec * 1000.0 + fc_fsec / 1000.0);
				break;

			case DTK_SECOND:
				if (fc_retnumeric)
					/*---
					 * tm->tm_sec + fsec / 1'000'000
					 * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
					 */
					PG_RETURN_NUMERIC(int64_div_fast_to_numeric(fc_tm->tm_sec * INT64CONST(1000000) + fc_fsec, 6));
				else
					PG_RETURN_FLOAT8(fc_tm->tm_sec + fc_fsec / 1000000.0);
				break;

			case DTK_MINUTE:
				fc_intresult = fc_tm->tm_min;
				break;

			case DTK_HOUR:
				fc_intresult = fc_tm->tm_hour;
				break;

			case DTK_DAY:
				fc_intresult = fc_tm->tm_mday;
				break;

			case DTK_MONTH:
				fc_intresult = fc_tm->tm_mon;
				break;

			case DTK_QUARTER:
				fc_intresult = (fc_tm->tm_mon - 1) / 3 + 1;
				break;

			case DTK_WEEK:
				fc_intresult = date2isoweek(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
				break;

			case DTK_YEAR:
				if (fc_tm->tm_year > 0)
					fc_intresult = fc_tm->tm_year;
				else
					/* 没有年份0，只有1 BC和1 AD */
					fc_intresult = fc_tm->tm_year - 1;
				break;

			case DTK_DECADE:
				/* 请参阅timestamp_part中的注释 */
				if (fc_tm->tm_year > 0)
					fc_intresult = fc_tm->tm_year / 10;
				else
					fc_intresult = -((8 - (fc_tm->tm_year - 1)) / 10);
				break;

			case DTK_CENTURY:
				/* 请参阅timestamp_part中的注释 */
				if (fc_tm->tm_year > 0)
					fc_intresult = (fc_tm->tm_year + 99) / 100;
				else
					fc_intresult = -((99 - (fc_tm->tm_year - 1)) / 100);
				break;

			case DTK_MILLENNIUM:
				/* 请参阅timestamp_part中的注释 */
				if (fc_tm->tm_year > 0)
					fc_intresult = (fc_tm->tm_year + 999) / 1000;
				else
					fc_intresult = -((999 - (fc_tm->tm_year - 1)) / 1000);
				break;

			case DTK_JULIAN:
				if (fc_retnumeric)
					PG_RETURN_NUMERIC(numeric_add_opt_error(int64_to_numeric(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday)),
															numeric_div_opt_error(int64_to_numeric(((((fc_tm->tm_hour * MINS_PER_HOUR) + fc_tm->tm_min) * SECS_PER_MINUTE) + fc_tm->tm_sec) * INT64CONST(1000000) + fc_fsec),
																				  int64_to_numeric(SECS_PER_DAY * INT64CONST(1000000)),
																				  NULL),
															NULL));
				else
					PG_RETURN_FLOAT8(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday) +
									 ((((fc_tm->tm_hour * MINS_PER_HOUR) + fc_tm->tm_min) * SECS_PER_MINUTE) +
									  fc_tm->tm_sec + (fc_fsec / 1000000.0)) / (double) SECS_PER_DAY);
				break;

			case DTK_ISOYEAR:
				fc_intresult = date2isoyear(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
				/* 调整 BC 年 */
				if (fc_intresult <= 0)
					fc_intresult -= 1;
				break;

			case DTK_DOW:
			case DTK_ISODOW:
				fc_intresult = j2day(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday));
				if (fc_val == DTK_ISODOW && fc_intresult == 0)
					fc_intresult = 7;
				break;

			case DTK_DOY:
				fc_intresult = (date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday)
							 - date2j(fc_tm->tm_year, 1, 1) + 1);
				break;

			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(TIMESTAMPTZOID))));
				fc_intresult = 0;
		}
	}
	else if (fc_type == RESERV)
	{
		switch (fc_val)
		{
			case DTK_EPOCH:
				fc_epoch = SetEpochTimestamp();
				/* (timestamp - epoch) / 1000000 */
				if (fc_retnumeric)
				{
					Numeric		fc_result;

					if (fc_timestamp < (PG_INT64_MAX + fc_epoch))
						fc_result = int64_div_fast_to_numeric(fc_timestamp - fc_epoch, 6);
					else
					{
						fc_result = numeric_div_opt_error(numeric_sub_opt_error(int64_to_numeric(fc_timestamp),
																			 int64_to_numeric(fc_epoch),
																			 NULL),
													   int64_to_numeric(1000000),
													   NULL);
						fc_result = DatumGetNumeric(DirectFunctionCall2(numeric_round,
																	 NumericGetDatum(fc_result),
																	 Int32GetDatum(6)));
					}
					PG_RETURN_NUMERIC(fc_result);
				}
				else
				{
					float8		fc_result;

					/* 尝试避免减法中的精度损失 */
					if (fc_timestamp < (PG_INT64_MAX + fc_epoch))
						fc_result = (fc_timestamp - fc_epoch) / 1000000.0;
					else
						fc_result = ((float8) fc_timestamp - fc_epoch) / 1000000.0;
					PG_RETURN_FLOAT8(fc_result);
				}
				break;

			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(TIMESTAMPTZOID))));
				fc_intresult = 0;
		}
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unit \"%s\" not recognized for type %s",
						fc_lowunits, format_type_be(TIMESTAMPTZOID))));

		fc_intresult = 0;
	}

	if (fc_retnumeric)
		PG_RETURN_NUMERIC(int64_to_numeric(fc_intresult));
	else
		PG_RETURN_FLOAT8(fc_intresult);
}

Datum timestamptz_part(PG_FUNCTION_ARGS)
{
	return fc_timestamptz_part_common(fcinfo, false);
}

Datum extract_timestamptz(PG_FUNCTION_ARGS)
{
	return fc_timestamptz_part_common(fcinfo, true);
}


/* interval_part() 和 extract_interval()
 * 从时间间隔中提取指定字段。
 */
static Datum fc_interval_part_common(PG_FUNCTION_ARGS, bool fc_retnumeric)
{
	text	   *fc_units = PG_GETARG_TEXT_PP(0);
	Interval   *fc_interval = PG_GETARG_INTERVAL_P(1);
	int64		fc_intresult;
	int			fc_type,
				fc_val;
	char	   *fc_lowunits;
	struct pg_itm fc_tt,
			   *fc_tm = &fc_tt;

	fc_lowunits = downcase_truncate_identifier(VARDATA_ANY(fc_units),
											VARSIZE_ANY_EXHDR(fc_units),
											false);

	fc_type = DecodeUnits(0, fc_lowunits, &fc_val);
	if (fc_type == UNKNOWN_FIELD)
		fc_type = DecodeSpecial(0, fc_lowunits, &fc_val);

	if (fc_type == UNITS)
	{
		interval2itm(*fc_interval, fc_tm);
		switch (fc_val)
		{
			case DTK_MICROSEC:
				fc_intresult = fc_tm->tm_sec * INT64CONST(1000000) + fc_tm->tm_usec;
				break;

			case DTK_MILLISEC:
				if (fc_retnumeric)
					/*---
					 * tm->tm_sec * 1000 + fsec / 1000
					 * = (tm->tm_sec * 1'000'000 + fsec) / 1000
					 */
					PG_RETURN_NUMERIC(int64_div_fast_to_numeric(fc_tm->tm_sec * INT64CONST(1000000) + fc_tm->tm_usec, 3));
				else
					PG_RETURN_FLOAT8(fc_tm->tm_sec * 1000.0 + fc_tm->tm_usec / 1000.0);
				break;

			case DTK_SECOND:
				if (fc_retnumeric)
					/*---
					 * tm->tm_sec + fsec / 1'000'000
					 * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
					 */
					PG_RETURN_NUMERIC(int64_div_fast_to_numeric(fc_tm->tm_sec * INT64CONST(1000000) + fc_tm->tm_usec, 6));
				else
					PG_RETURN_FLOAT8(fc_tm->tm_sec + fc_tm->tm_usec / 1000000.0);
				break;

			case DTK_MINUTE:
				fc_intresult = fc_tm->tm_min;
				break;

			case DTK_HOUR:
				fc_intresult = fc_tm->tm_hour;
				break;

			case DTK_DAY:
				fc_intresult = fc_tm->tm_mday;
				break;

			case DTK_MONTH:
				fc_intresult = fc_tm->tm_mon;
				break;

			case DTK_QUARTER:
				fc_intresult = (fc_tm->tm_mon / 3) + 1;
				break;

			case DTK_YEAR:
				fc_intresult = fc_tm->tm_year;
				break;

			case DTK_DECADE:
				/* 注意：C 除法可能有负余数 */
				fc_intresult = fc_tm->tm_year / 10;
				break;

			case DTK_CENTURY:
				/* 注意：C 除法可能有负余数 */
				fc_intresult = fc_tm->tm_year / 100;
				break;

			case DTK_MILLENNIUM:
				/* 注意：C 除法可能有负余数 */
				fc_intresult = fc_tm->tm_year / 1000;
				break;

			default:
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unit \"%s\" not supported for type %s",
								fc_lowunits, format_type_be(INTERVALOID))));
				fc_intresult = 0;
		}
	}
	else if (fc_type == RESERV && fc_val == DTK_EPOCH)
	{
		if (fc_retnumeric)
		{
			Numeric		fc_result;
			int64		fc_secs_from_day_month;
			int64		fc_val;

			/*
			 * 即使 DAYS_PER_YEAR 是小数，也要在整数算术中进行此计算，
			 * 将所有值乘以 4，然后在最后再除以 4。这依赖于 DAYS_PER_YEAR
			 * 是 0.25 的倍数，以及 SECS_PER_DAY 是 4 的倍数。
			 */
			fc_secs_from_day_month = ((int64) (4 * DAYS_PER_YEAR) * (fc_interval->month / MONTHS_PER_YEAR) +
								   (int64) (4 * DAYS_PER_MONTH) * (fc_interval->month % MONTHS_PER_YEAR) +
								   (int64) 4 * fc_interval->day) * (SECS_PER_DAY / 4);

			/*---
			 * 结果 = secs_from_day_month + interval->time / 1'000'000
			 * = (secs_from_day_month * 1'000'000 + interval->time) / 1'000'000
			 */

			/*
			 * 尝试在 int64 内进行计算；如果发生溢出，则在
			 * 数字中进行计算（较慢）。这个溢出大约发生在 10^9 天，所以在实践中并不常见。
			 */
			if (!pg_mul_s64_overflow(fc_secs_from_day_month, 1000000, &fc_val) &&
				!pg_add_s64_overflow(fc_val, fc_interval->time, &fc_val))
				fc_result = int64_div_fast_to_numeric(fc_val, 6);
			else
				fc_result =
					numeric_add_opt_error(int64_div_fast_to_numeric(fc_interval->time, 6),
										  int64_to_numeric(fc_secs_from_day_month),
										  NULL);

			PG_RETURN_NUMERIC(fc_result);
		}
		else
		{
			float8		fc_result;

			fc_result = fc_interval->time / 1000000.0;
			fc_result += ((double) DAYS_PER_YEAR * SECS_PER_DAY) * (fc_interval->month / MONTHS_PER_YEAR);
			fc_result += ((double) DAYS_PER_MONTH * SECS_PER_DAY) * (fc_interval->month % MONTHS_PER_YEAR);
			fc_result += ((double) SECS_PER_DAY) * fc_interval->day;

			PG_RETURN_FLOAT8(fc_result);
		}
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unit \"%s\" not recognized for type %s",
						fc_lowunits, format_type_be(INTERVALOID))));
		fc_intresult = 0;
	}

	if (fc_retnumeric)
		PG_RETURN_NUMERIC(int64_to_numeric(fc_intresult));
	else
		PG_RETURN_FLOAT8(fc_intresult);
}

Datum interval_part(PG_FUNCTION_ARGS)
{
	return fc_interval_part_common(fcinfo, false);
}

Datum extract_interval(PG_FUNCTION_ARGS)
{
	return fc_interval_part_common(fcinfo, true);
}


/*	timestamp_zone()
 *	使用指定时区编码时间戳类型。
 *	此函数实际上是 timestamp2timestamptz()，只是
 *	不是转移到全球时区，而是转移到指定的时区。
 *	这与其他 AT TIME ZONE 情况不同，因为不是
 *	转移到新的时区，而是将时间设置为指定的时区。
 */
Datum timestamp_zone(PG_FUNCTION_ARGS)
{
	text	   *fc_zone = PG_GETARG_TEXT_PP(0);
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(1);
	TimestampTz fc_result;
	int			fc_tz;
	char		fc_tzname[TZ_STRLEN_MAX + 1];
	char	   *fc_lowzone;
	int			fc_type,
				fc_val;
	pg_tz	   *fc_tzp;
	struct pg_tm fc_tm;
	fsec_t		fc_fsec;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMPTZ(fc_timestamp);

	/*
	 * 查找请求的时区。首先，我们查看时区缩写表（处理类似“EST”的情况），如果失败，则查看时区数据库（处理类似“America/New_York”的情况）。（这与时间戳输入检查情况的顺序相匹配；这很重要，因为时区数据库不明智地使用了几个与偏移缩写相同的区域名称。）
	 */
	text_to_cstring_buffer(fc_zone, fc_tzname, sizeof(fc_tzname));

	/* DecodeTimezoneAbbrev需要小写输入 */
	fc_lowzone = downcase_truncate_identifier(fc_tzname,
										   strlen(fc_tzname),
										   false);

	fc_type = DecodeTimezoneAbbrev(0, fc_lowzone, &fc_val, &fc_tzp);

	if (fc_type == TZ || fc_type == DTZ)
	{
		/* 固定偏移缩写 */
		fc_tz = fc_val;
		fc_result = fc_dt2local(fc_timestamp, fc_tz);
	}
	else if (fc_type == DYNTZ)
	{
		/* 动态偏移缩写，通过指定的时间解析 */
		if (timestamp2tm(fc_timestamp, NULL, &fc_tm, &fc_fsec, NULL, fc_tzp) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));
		fc_tz = -DetermineTimeZoneAbbrevOffset(&fc_tm, fc_tzname, fc_tzp);
		fc_result = fc_dt2local(fc_timestamp, fc_tz);
	}
	else
	{
		/* 尝试将其作为完整区域名称 */
		fc_tzp = pg_tzset(fc_tzname);
		if (fc_tzp)
		{
			/* 应用时区更改 */
			if (timestamp2tm(fc_timestamp, NULL, &fc_tm, &fc_fsec, NULL, fc_tzp) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
			fc_tz = DetermineTimeZoneOffset(&fc_tm, fc_tzp);
			if (tm2timestamp(&fc_tm, fc_fsec, &fc_tz, &fc_result) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
		}
		else
		{
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("time zone \"%s\" not recognized", fc_tzname)));
			fc_result = 0;			/* 保持编译器安静 */
		}
	}

	if (!IS_VALID_TIMESTAMP(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	PG_RETURN_TIMESTAMPTZ(fc_result);
}

/* timestamp_izone()
 * 使用指定时间间隔作为时区编码时间戳类型。
 */
Datum timestamp_izone(PG_FUNCTION_ARGS)
{
	Interval   *fc_zone = PG_GETARG_INTERVAL_P(0);
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(1);
	TimestampTz fc_result;
	int			fc_tz;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMPTZ(fc_timestamp);

	if (fc_zone->month != 0 || fc_zone->day != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("interval time zone \"%s\" must not include months or days",
						DatumGetCString(DirectFunctionCall1(interval_out,
															PointerGetDatum(fc_zone))))));

	fc_tz = fc_zone->time / USECS_PER_SEC;

	fc_result = fc_dt2local(fc_timestamp, fc_tz);

	if (!IS_VALID_TIMESTAMP(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	PG_RETURN_TIMESTAMPTZ(fc_result);
}								/* timestamp_izone() */

/* TimestampTimestampTzRequiresRewrite()
 *
 * 如果 TimeZone GUC 设置导致 timestamp_timestamptz 和
 * timestamptz_timestamp 无效，则返回 false，返回值的位与参数相同。
 * 由于项目约定假设 GUC 的更改不比 STABLE 函数的更改更频繁，因此答案在这个时间段内是有效的。
 */
bool TimestampTimestampTzRequiresRewrite(void)
{
	long		fc_offset;

	if (pg_get_timezone_offset(session_timezone, &fc_offset) && fc_offset == 0)
		return false;
	return true;
}

/* timestamp_timestamptz()
 * 将本地时间戳转换为 GMT 时间戳
 */
Datum timestamp_timestamptz(PG_FUNCTION_ARGS)
{
	Timestamp	fc_timestamp = PG_GETARG_TIMESTAMP(0);

	PG_RETURN_TIMESTAMPTZ(fc_timestamp2timestamptz(fc_timestamp));
}

/*
 * 将时间戳转换为带时区的时间戳。
 *
 * 在成功转换时，如果 overflow 不为 NULL，则将其设置为零。
 *
 * 如果时间戳是有限的但超出了 timestamptz 的有效范围，则：
 * 如果 overflow 为空，我们将抛出一个超出范围的错误。
 * 如果 overflow 不为空，我们将在那里存储 +1 或 -1 表示溢出的符号，并返回相应的 timestamptz 无限值。
 */
TimestampTz timestamp2timestamptz_opt_overflow(Timestamp fc_timestamp, int *fc_overflow)
{
	TimestampTz fc_result;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	fsec_t		fc_fsec;
	int			fc_tz;

	if (fc_overflow)
		*fc_overflow = 0;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		return fc_timestamp;

	/* 我们不期望这个失败，但形式上检查一下 */
	if (timestamp2tm(fc_timestamp, NULL, fc_tm, &fc_fsec, NULL, NULL) == 0)
	{
		fc_tz = DetermineTimeZoneOffset(fc_tm, session_timezone);

		fc_result = fc_dt2local(fc_timestamp, -fc_tz);

		if (IS_VALID_TIMESTAMP(fc_result))
		{
			return fc_result;
		}
		else if (fc_overflow)
		{
			if (fc_result < MIN_TIMESTAMP)
			{
				*fc_overflow = -1;
				TIMESTAMP_NOBEGIN(fc_result);
			}
			else
			{
				*fc_overflow = 1;
				TIMESTAMP_NOEND(fc_result);
			}
			return fc_result;
		}
	}

	ereport(ERROR,
			(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
			 errmsg("timestamp out of range")));

	return 0;
}

/*
 * 促使时间戳提升为 timestamptz，并对溢出抛出错误。
 */
static TimestampTz fc_timestamp2timestamptz(Timestamp fc_timestamp)
{
	return timestamp2timestamptz_opt_overflow(fc_timestamp, NULL);
}

/* timestamptz_timestamp()
 * 将 GMT 下的时间戳转换为本地时间戳
 */
Datum timestamptz_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(0);

	PG_RETURN_TIMESTAMP(fc_timestamptz2timestamp(fc_timestamp));
}

static Timestamp fc_timestamptz2timestamp(TimestampTz fc_timestamp)
{
	Timestamp	fc_result;
	struct pg_tm fc_tt,
			   *fc_tm = &fc_tt;
	fsec_t		fc_fsec;
	int			fc_tz;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		fc_result = fc_timestamp;
	else
	{
		if (timestamp2tm(fc_timestamp, &fc_tz, fc_tm, &fc_fsec, NULL, NULL) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));
		if (tm2timestamp(fc_tm, fc_fsec, NULL, &fc_result) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));
	}
	return fc_result;
}

/* timestamptz_zone()
 * 在指定时区评估带时区的时间戳类型。
 * 返回不带时区的时间戳。
 */
Datum timestamptz_zone(PG_FUNCTION_ARGS)
{
	text	   *fc_zone = PG_GETARG_TEXT_PP(0);
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(1);
	Timestamp	fc_result;
	int			fc_tz;
	char		fc_tzname[TZ_STRLEN_MAX + 1];
	char	   *fc_lowzone;
	int			fc_type,
				fc_val;
	pg_tz	   *fc_tzp;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMP(fc_timestamp);

	/*
	 * 查找请求的时区。首先，我们查看时区缩写表（处理类似“EST”的情况），如果失败，则查看时区数据库（处理类似“America/New_York”的情况）。（这与时间戳输入检查情况的顺序相匹配；这很重要，因为时区数据库不明智地使用了几个与偏移缩写相同的区域名称。）
	 */
	text_to_cstring_buffer(fc_zone, fc_tzname, sizeof(fc_tzname));

	/* DecodeTimezoneAbbrev需要小写输入 */
	fc_lowzone = downcase_truncate_identifier(fc_tzname,
										   strlen(fc_tzname),
										   false);

	fc_type = DecodeTimezoneAbbrev(0, fc_lowzone, &fc_val, &fc_tzp);

	if (fc_type == TZ || fc_type == DTZ)
	{
		/* 固定偏移缩写 */
		fc_tz = -fc_val;
		fc_result = fc_dt2local(fc_timestamp, fc_tz);
	}
	else if (fc_type == DYNTZ)
	{
		/* 动态偏移缩写，通过指定的时间解析 */
		int			fc_isdst;

		fc_tz = DetermineTimeZoneAbbrevOffsetTS(fc_timestamp, fc_tzname, fc_tzp, &fc_isdst);
		fc_result = fc_dt2local(fc_timestamp, fc_tz);
	}
	else
	{
		/* 尝试将其作为完整区域名称 */
		fc_tzp = pg_tzset(fc_tzname);
		if (fc_tzp)
		{
			/* 应用时区更改 */
			struct pg_tm fc_tm;
			fsec_t		fc_fsec;

			if (timestamp2tm(fc_timestamp, &fc_tz, &fc_tm, &fc_fsec, NULL, fc_tzp) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
			if (tm2timestamp(&fc_tm, fc_fsec, NULL, &fc_result) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
						 errmsg("timestamp out of range")));
		}
		else
		{
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("time zone \"%s\" not recognized", fc_tzname)));
			fc_result = 0;			/* 保持编译器安静 */
		}
	}

	if (!IS_VALID_TIMESTAMP(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	PG_RETURN_TIMESTAMP(fc_result);
}

/* timestamptz_izone()
 * 使用指定时间间隔作为时区，编码带时区的时间戳类型。
 * 返回不带时区的时间戳。
 */
Datum timestamptz_izone(PG_FUNCTION_ARGS)
{
	Interval   *fc_zone = PG_GETARG_INTERVAL_P(0);
	TimestampTz fc_timestamp = PG_GETARG_TIMESTAMPTZ(1);
	Timestamp	fc_result;
	int			fc_tz;

	if (TIMESTAMP_NOT_FINITE(fc_timestamp))
		PG_RETURN_TIMESTAMP(fc_timestamp);

	if (fc_zone->month != 0 || fc_zone->day != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("interval time zone \"%s\" must not include months or days",
						DatumGetCString(DirectFunctionCall1(interval_out,
															PointerGetDatum(fc_zone))))));

	fc_tz = -(fc_zone->time / USECS_PER_SEC);

	fc_result = fc_dt2local(fc_timestamp, fc_tz);

	if (!IS_VALID_TIMESTAMP(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	PG_RETURN_TIMESTAMP(fc_result);
}

/* generate_series_timestamp()
 * 通过步长生成从开始到结束的时间戳集合
 */
Datum generate_series_timestamp(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	generate_series_timestamp_fctx *fc_fctx;
	Timestamp	fc_result;

	/* 仅在函数的第一次调用时完成的工作 */
	if (SRF_IS_FIRSTCALL())
	{
		Timestamp	fc_start = PG_GETARG_TIMESTAMP(0);
		Timestamp	fc_finish = PG_GETARG_TIMESTAMP(1);
		Interval   *fc_step = PG_GETARG_INTERVAL_P(2);
		MemoryContext fc_oldcontext;
		Interval	fc_interval_zero;

		/* 为跨调用持久性创建一个函数上下文 */
		fc_funcctx = SRF_FIRSTCALL_INIT();

		/*
		 * 切换到适合多次函数调用的内存上下文
		 */
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		/* 为用户上下文分配内存 */
		fc_fctx = (generate_series_timestamp_fctx *)
			palloc(sizeof(generate_series_timestamp_fctx));

		/*
		 * 使用 fctx 在调用之间保持状态。用原始起始值初始化当前值
		 */
		fc_fctx->current = fc_start;
		fc_fctx->finish = fc_finish;
		fc_fctx->step = *fc_step;

		/* 确定时间间隔的符号 */
		MemSet(&fc_interval_zero, 0, sizeof(Interval));
		fc_fctx->step_sign = fc_interval_cmp_internal(&fc_fctx->step, &fc_interval_zero);

		if (fc_fctx->step_sign == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("step size cannot equal zero")));

		fc_funcctx->user_fctx = fc_fctx;
		MemoryContextSwitchTo(fc_oldcontext);
	}

	/* 每次调用函数时完成的工作 */
	fc_funcctx = SRF_PERCALL_SETUP();

	/*
	 * 获取保存的状态，并将当前值作为本次迭代的结果
	 */
	fc_fctx = fc_funcctx->user_fctx;
	fc_result = fc_fctx->current;

	if (fc_fctx->step_sign > 0 ?
		timestamp_cmp_internal(fc_result, fc_fctx->finish) <= 0 :
		timestamp_cmp_internal(fc_result, fc_fctx->finish) >= 0)
	{
		/* 增加当前值以准备下一次迭代 */
		fc_fctx->current = DatumGetTimestamp(DirectFunctionCall2(timestamp_pl_interval,
															  TimestampGetDatum(fc_fctx->current),
															  PointerGetDatum(&fc_fctx->step)));

		/* 当还有更多内容要发送时执行 */
		SRF_RETURN_NEXT(fc_funcctx, TimestampGetDatum(fc_result));
	}
	else
	{
		/* 当没有更多内容时执行 */
		SRF_RETURN_DONE(fc_funcctx);
	}
}


/* generate_series_timestamptz()
 * 通过步长从开始到结束生成时间戳集合
 */
Datum generate_series_timestamptz(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	generate_series_timestamptz_fctx *fc_fctx;
	TimestampTz fc_result;

	/* 仅在函数的第一次调用时完成的工作 */
	if (SRF_IS_FIRSTCALL())
	{
		TimestampTz fc_start = PG_GETARG_TIMESTAMPTZ(0);
		TimestampTz fc_finish = PG_GETARG_TIMESTAMPTZ(1);
		Interval   *fc_step = PG_GETARG_INTERVAL_P(2);
		MemoryContext fc_oldcontext;
		Interval	fc_interval_zero;

		/* 为跨调用持久性创建一个函数上下文 */
		fc_funcctx = SRF_FIRSTCALL_INIT();

		/*
		 * 切换到适合多次函数调用的内存上下文
		 */
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		/* 为用户上下文分配内存 */
		fc_fctx = (generate_series_timestamptz_fctx *)
			palloc(sizeof(generate_series_timestamptz_fctx));

		/*
		 * 使用 fctx 在调用之间保持状态。用原始起始值初始化当前值
		 */
		fc_fctx->current = fc_start;
		fc_fctx->finish = fc_finish;
		fc_fctx->step = *fc_step;

		/* 确定时间间隔的符号 */
		MemSet(&fc_interval_zero, 0, sizeof(Interval));
		fc_fctx->step_sign = fc_interval_cmp_internal(&fc_fctx->step, &fc_interval_zero);

		if (fc_fctx->step_sign == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("step size cannot equal zero")));

		fc_funcctx->user_fctx = fc_fctx;
		MemoryContextSwitchTo(fc_oldcontext);
	}

	/* 每次调用函数时完成的工作 */
	fc_funcctx = SRF_PERCALL_SETUP();

	/*
	 * 获取保存的状态，并将当前值作为本次迭代的结果
	 */
	fc_fctx = fc_funcctx->user_fctx;
	fc_result = fc_fctx->current;

	if (fc_fctx->step_sign > 0 ?
		timestamp_cmp_internal(fc_result, fc_fctx->finish) <= 0 :
		timestamp_cmp_internal(fc_result, fc_fctx->finish) >= 0)
	{
		/* 增加当前值以准备下一次迭代 */
		fc_fctx->current = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,
																TimestampTzGetDatum(fc_fctx->current),
																PointerGetDatum(&fc_fctx->step)));

		/* 当还有更多内容要发送时执行 */
		SRF_RETURN_NEXT(fc_funcctx, TimestampTzGetDatum(fc_result));
	}
	else
	{
		/* 当没有更多内容时执行 */
		SRF_RETURN_DONE(fc_funcctx);
	}
}
